@kudoai/chatgpt.js 3.6.0 → 3.6.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,12 +1,63 @@
1
- // Requires lib/chatgpt.js + lib/dom.js
1
+ // Requires lib/chatgpt.js + lib/dom.js + app + env
2
2
 
3
3
  window.modals = {
4
+ import(deps) { Object.assign(this.imports = this.imports || {}, deps) },
5
+
4
6
  stack: [], // of types of undismissed modals
5
7
  get class() { return `${this.imports.app.cssPrefix}-modal` },
6
8
 
7
- imports: {
8
- import(deps) { // { app, env }
9
- for (const depName in deps) this[depName] = deps[depName] }
9
+ about() {
10
+
11
+ // Show modal
12
+ const aboutModal = this.alert(
13
+ `${this.imports.app.symbol} ${chrome.runtime.getManifest().name}`, // title
14
+ '🧠 Author: ' // msg
15
+ + `<a href="${this.imports.app.author.url}">${this.imports.app.author.name}</a> `
16
+ + `& <a href="${this.imports.app.urls.contributors}">contributors</a>\n`
17
+ + `🏷️ Version: <span class="about-em">${this.imports.app.version}</span>\n`
18
+ + '📜 Open source code: '
19
+ + `<a href="${this.imports.app.urls.gitHub}" target="_blank" rel="nopener">`
20
+ + this.imports.app.urls.gitHub + '</a>\n'
21
+ + '⚡ Powered by: '
22
+ + `<a href="${this.imports.app.urls.chatgptJS}" target="_blank" rel="noopener">chatgpt.js</a>`,
23
+ [ function getSupport(){}, function rateUs(){}, function moreAiExtensions(){} ], // button labels
24
+ '', 656 // modal width
25
+ )
26
+
27
+ // Format text
28
+ aboutModal.querySelector('h2').style.cssText = (
29
+ 'text-align: center ; font-size: 51px ; line-height: 46px ; padding: 15px 0' )
30
+ aboutModal.querySelector('p').style.cssText = (
31
+ 'text-align: center ; overflow-wrap: anywhere ;'
32
+ + `margin: ${ this.imports.env.browser.isPortrait ? '6px 0 -16px' : '3px 0 0' }` )
33
+
34
+ // Hack buttons
35
+ aboutModal.querySelector('.modal-buttons').style.justifyContent = 'center'
36
+ aboutModal.querySelectorAll('button').forEach(btn => {
37
+ btn.style.cssText = 'height: 55px ; min-width: 136px ; text-align: center'
38
+
39
+ // Replace buttons w/ clones that don't dismiss modal
40
+ const btnClone = btn.cloneNode(true)
41
+ btn.parentNode.replaceChild(btnClone, btn) ; btn = btnClone
42
+ btn.onclick = () => this.safeWinOpen(
43
+ btn.textContent == 'Get Support' ? `${modals.imports.app.urls.gitHub}/issues`
44
+ : btn.textContent == 'Rate Us' ? `${modals.imports.app.urls.gitHub}/discussions`
45
+ : modals.imports.app.urls.relatedExtensions
46
+ )
47
+
48
+ // Prepend emoji
49
+ if (/support/i.test(btn.textContent))
50
+ btn.textContent = '🧠 ' + btn.textContent
51
+ else if (/rate/i.test(btn.textContent))
52
+ btn.textContent = '⭐ ' + btn.textContent
53
+ else if (/extensions/i.test(btn.textContent))
54
+ btn.textContent = '🧠 ' + btn.textContent
55
+
56
+ // Hide Dismiss button
57
+ else btn.style.display = 'none'
58
+ })
59
+
60
+ return aboutModal
10
61
  },
11
62
 
12
63
  alert(title = '', msg = '', btns = '', checkbox = '', width = '') { // generic one from chatgpt.alert()
@@ -16,6 +67,30 @@ window.modals = {
16
67
  return alert
17
68
  },
18
69
 
70
+ init(modal) {
71
+ if (!modal) return // to support non-div this.open()s
72
+ if (!this.styles) this.stylize() // to init/append stylesheet
73
+ modal.classList.add('no-user-select', this.class) ; modal.parentNode.classList.add(`${this.class}-bg`)
74
+ dom.addRisingParticles(modal)
75
+ },
76
+
77
+ observeRemoval(modal, modalType) { // to maintain stack for proper nav
78
+ const modalBG = modal.parentNode
79
+ new MutationObserver(([mutation], obs) => {
80
+ mutation.removedNodes.forEach(removedNode => { if (removedNode == modalBG) {
81
+ if (this.stack[0] == modalType) { // new modal not launched, implement nav back logic
82
+ this.stack.shift() // remove this modal type from stack 1st
83
+ const prevModalType = this.stack[0]
84
+ if (prevModalType) { // open it
85
+ this.stack.shift() // remove type from stack since re-added on open
86
+ this.open(prevModalType)
87
+ }
88
+ }
89
+ obs.disconnect()
90
+ }})
91
+ }).observe(modalBG.parentNode, { childList: true, subtree: true })
92
+ },
93
+
19
94
  open(modalType) {
20
95
  const modal = this[modalType]() // show modal
21
96
  this.stack.unshift(modalType) // add to stack
@@ -23,12 +98,7 @@ window.modals = {
23
98
  this.observeRemoval(modal, modalType) // to maintain stack for proper nav
24
99
  },
25
100
 
26
- init(modal) {
27
- if (!modal) return // to support non-div this.open()s
28
- if (!this.styles) this.stylize() // to init/append stylesheet
29
- modal.classList.add('no-user-select', this.class) ; modal.parentNode.classList.add(`${this.class}-bg`)
30
- dom.addRisingParticles(modal)
31
- },
101
+ safeWinOpen(url) { open(url, '_blank', 'noopener') }, // to prevent backdoor vulnerabilities
32
102
 
33
103
  stylize() {
34
104
  if (!this.styles) {
@@ -77,78 +147,5 @@ window.modals = {
77
147
  `.${this.class} .modal-buttons { margin-left: -13px !important }` : '' )
78
148
  + `.about-em { color: ${ this.imports.env.ui.scheme == 'dark' ? 'white' : 'green' } !important }`
79
149
  )
80
- },
81
-
82
- observeRemoval(modal, modalType) { // to maintain stack for proper nav
83
- const modalBG = modal.parentNode
84
- new MutationObserver(([mutation], obs) => {
85
- mutation.removedNodes.forEach(removedNode => { if (removedNode == modalBG) {
86
- if (this.stack[0] == modalType) { // new modal not launched, implement nav back logic
87
- this.stack.shift() // remove this modal type from stack 1st
88
- const prevModalType = this.stack[0]
89
- if (prevModalType) { // open it
90
- this.stack.shift() // remove type from stack since re-added on open
91
- this.open(prevModalType)
92
- }
93
- }
94
- obs.disconnect()
95
- }})
96
- }).observe(modalBG.parentNode, { childList: true, subtree: true })
97
- },
98
-
99
- about() {
100
-
101
- // Show modal
102
- const aboutModal = this.alert(
103
- `${this.imports.app.symbol} ${chrome.runtime.getManifest().name}`, // title
104
- '🧠 Author: ' // msg
105
- + `<a href="${this.imports.app.author.url}">${this.imports.app.author.name}</a> `
106
- + `& <a href="${this.imports.app.urls.contributors}">contributors</a>\n`
107
- + `🏷️ Version: <span class="about-em">${this.imports.app.version}</span>\n`
108
- + '📜 Open source code: '
109
- + `<a href="${this.imports.app.urls.gitHub}" target="_blank" rel="nopener">`
110
- + this.imports.app.urls.gitHub + '</a>\n'
111
- + '⚡ Powered by: '
112
- + `<a href="${this.imports.app.urls.chatgptJS}" target="_blank" rel="noopener">chatgpt.js</a>`,
113
- [ function getSupport(){}, function rateUs(){}, function moreAiExtensions(){} ], // button labels
114
- '', 656 // modal width
115
- )
116
-
117
- // Format text
118
- aboutModal.querySelector('h2').style.cssText = (
119
- 'text-align: center ; font-size: 51px ; line-height: 46px ; padding: 15px 0' )
120
- aboutModal.querySelector('p').style.cssText = (
121
- 'text-align: center ; overflow-wrap: anywhere ;'
122
- + `margin: ${ this.imports.env.browser.isPortrait ? '6px 0 -16px' : '3px 0 0' }` )
123
-
124
- // Hack buttons
125
- aboutModal.querySelector('.modal-buttons').style.justifyContent = 'center'
126
- aboutModal.querySelectorAll('button').forEach(btn => {
127
- btn.style.cssText = 'height: 55px ; min-width: 136px ; text-align: center'
128
-
129
- // Replace buttons w/ clones that don't dismiss modal
130
- const btnClone = btn.cloneNode(true)
131
- btn.parentNode.replaceChild(btnClone, btn) ; btn = btnClone
132
- btn.onclick = () => this.safeWinOpen(
133
- btn.textContent == 'Get Support' ? `${modals.imports.app.urls.gitHub}/issues`
134
- : btn.textContent == 'Rate Us' ? `${modals.imports.app.urls.gitHub}/discussions`
135
- : modals.imports.app.urls.relatedExtensions
136
- )
137
-
138
- // Prepend emoji
139
- if (/support/i.test(btn.textContent))
140
- btn.textContent = '🧠 ' + btn.textContent
141
- else if (/rate/i.test(btn.textContent))
142
- btn.textContent = '⭐ ' + btn.textContent
143
- else if (/extensions/i.test(btn.textContent))
144
- btn.textContent = '🧠 ' + btn.textContent
145
-
146
- // Hide Dismiss button
147
- else btn.style.display = 'none'
148
- })
149
-
150
- return aboutModal
151
- },
152
-
153
- safeWinOpen(url) { open(url, '_blank', 'noopener') } // to prevent backdoor vulnerabilities
150
+ }
154
151
  };
@@ -15,8 +15,8 @@
15
15
  const { app } = await chrome.storage.sync.get('app')
16
16
 
17
17
  // Export DEPENDENCIES to imported resources
18
- dom.imports.import({ env }) // for env.ui.scheme
19
- modals.imports.import({ app, env }) // for app data + env.ui.scheme
18
+ dom.import({ env }) // for env.ui.scheme
19
+ modals.import({ app, env }) // for app data + env.<browser|ui> flags
20
20
 
21
21
  // Add CHROME MSG listener
22
22
  chrome.runtime.onMessage.addListener(req => { // from service-worker.js + popup/index.html
@@ -97,7 +97,8 @@ const chatgpt = {
97
97
  if (event.button != 0) return // prevent non-left-click drag
98
98
  if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
99
99
  chatgpt.draggableElem = event.currentTarget
100
- chatgpt.draggableElem.style.cursor = 'grabbing'
100
+ Object.assign(chatgpt.draggableElem.style, {
101
+ cursor: 'grabbing', transition: '0.1s', willChange: 'transform', transform: 'scale(1.05)' })
101
102
  event.preventDefault(); // prevent sub-elems like icons being draggable
102
103
  ['mousemove', 'mouseup'].forEach(eventType =>
103
104
  document.addEventListener(eventType, handlers.drag[eventType]))
@@ -114,7 +115,8 @@ const chatgpt = {
114
115
  },
115
116
 
116
117
  mouseup() { // remove listeners, reset chatgpt.draggableElem
117
- chatgpt.draggableElem.style.cursor = 'inherit';
118
+ Object.assign(chatgpt.draggableElem.style, {
119
+ cursor: 'inherit', transition: 'inherit', willChange: 'auto', transform: 'scale(1)' });
118
120
  ['mousemove', 'mouseup'].forEach(eventType =>
119
121
  document.removeEventListener(eventType, handlers.drag[eventType]))
120
122
  chatgpt.draggableElem = null
@@ -165,19 +167,22 @@ const chatgpt = {
165
167
  + `background-color: ${ scheme == 'dark' ? 'black' : 'white' };`
166
168
  + 'transform: translateX(-3px) translateY(7px) ;' // offset to move-in from
167
169
  + 'max-width: 75vw ; word-wrap: break-word ; border-radius: 15px ;'
168
- + 'padding: 20px ; margin: 12px 23px ; box-shadow: 0 30px 60px rgba(0,0,0,0.12) ;'
170
+ + 'padding: 20px ; margin: 12px 23px ;'
171
+ + `--shadow: 0 30px 60px rgba(0,0,0,0.12) ; box-shadow: var(--shadow) ;
172
+ -webkit-box-shadow: var(--shadow) ; -moz-box-shadow: var(--shadow) ;`
169
173
  + 'user-select: none ; -webkit-user-select: none ; -moz-user-select: none ; -o-user-select: none ;'
170
174
  + '-ms-user-select: none ;'
171
175
  + 'transition: var(--transition) ;' // for fade-in + move-in
172
176
  + '-webkit-transition: var(--transition) ; -moz-transition: var(--transition) ;'
173
177
  + '-o-transition: var(--transition) ; -ms-transition: var(--transition) }'
174
- + '.chatgpt-modal h2 { margin-bottom: 9px }'
175
- + `.chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}`
176
- + '.chatgpt-modal a:hover { text-decoration: underline }'
177
- + '.chatgpt-modal.animated > div { z-index: 13456 ; opacity: 0.98 ; transform: translateX(0) translateY(0) }'
178
- + '@keyframes alert-zoom-fade-out {'
179
- + '0% { opacity: 1 } 50% { opacity: 0.25 ; transform: scale(1.05) }'
180
- + '100% { opacity: 0 ; transform: scale(1.35) }}'
178
+ + `.chatgpt-modal h2 { margin-bottom: 9px }
179
+ .chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}
180
+ .chatgpt-modal a:hover { text-decoration: underline }
181
+ .chatgpt-modal.animated > div {
182
+ z-index: 13456 ; opacity: 0.98 ; transform: translateX(0) translateY(0) }
183
+ @keyframes alert-zoom-fade-out {
184
+ 0% { opacity: 1 } 50% { opacity: 0.25 ; transform: scale(1.05) }
185
+ 100% { opacity: 0 ; transform: scale(1.35) }}`
181
186
 
182
187
  // Button styles
183
188
  + '.modal-buttons { display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;'
@@ -190,9 +195,11 @@ const chatgpt = {
190
195
  + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
191
196
  + `background: ${ scheme == 'dark' ? 'white' : 'black' } ;`
192
197
  + `color: ${ scheme == 'dark' ? 'black' : 'white' }}`
193
- + '.chatgpt-modal button:hover { color: #3d5d71 ; border-color: #6d9cb9 ;'
194
- + 'background-color: ' + ( scheme == 'dark' ? '#00cfff' : '#9cdaff' ) + ';'
195
- + 'box-shadow: 2px 1px ' + ( scheme == 'dark' ? '54px #00cfff' : '30px #9cdaff' ) + '}'
198
+ + `.chatgpt-modal button:hover {
199
+ color: #3d5d71 ; border-color: #6d9cb9 ;
200
+ background-color: ${ scheme == 'dark' ? '#00cfff' : '#9cdaff' };
201
+ --shadow: 2px 1px ${ scheme == 'dark' ? '54px #00cfff' : '30px #9cdaff' };
202
+ box-shadow: var(--shadow) ; box-shadow: var(--shadow) ; box-shadow: var(--shadow) }`
196
203
  + '.modal-close-btn {'
197
204
  + 'cursor: pointer ; width: 29px ; height: 29px ; border-radius: 17px ;'
198
205
  + 'float: right ; position: relative ; right: -6px ; top: -5px }'
@@ -200,17 +207,18 @@ const chatgpt = {
200
207
  + `.modal-close-btn:hover { background-color: #f2f2f2${ scheme == 'dark' ? '00' : '' }}`
201
208
 
202
209
  // Checkbox styles
203
- + '.chatgpt-modal .checkbox-group { display: flex ; margin-top: -18px }'
204
- + '.chatgpt-modal .checkbox-group label {'
205
- + 'font-size: .7rem ; margin: -.04rem 0 0px .3rem ;'
206
- + `color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}`
207
- + '.chatgpt-modal input[type=checkbox] { transform: scale(0.7) ;'
208
- + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' }}`
209
- + '.chatgpt-modal input[type=checkbox]:checked {'
210
- + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
211
- + 'background-color: black ; position: inherit }'
212
- + '.chatgpt-modal input[type=checkbox]:focus { outline: none ; box-shadow: none }'
213
- );
210
+ + `.chatgpt-modal .checkbox-group { margin-top: 15px }
211
+ .chatgpt-modal .checkbox-group label {
212
+ font-size: .7rem ; margin: -.04rem 0 0px .3rem
213
+ color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}
214
+ .chatgpt-modal input[type=checkbox] { transform: scale(0.7) ;
215
+ border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' }}
216
+ .chatgpt-modal input[type=checkbox]:checked {
217
+ border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;
218
+ background-color: black ; position: inherit }
219
+ .chatgpt-modal input[type=checkbox]:focus {
220
+ outline: none ; box-shadow: none ; -webkit-box-shadow: none ; -moz-box-shadow: none }`
221
+ )
214
222
  }
215
223
 
216
224
  // Insert text into elements
@@ -277,7 +285,7 @@ const chatgpt = {
277
285
  closeSVG.append(closeSVGpath); closeBtn.append(closeSVG);
278
286
 
279
287
  // Assemble/append div
280
- const modalElems = [closeBtn, modalTitle, modalMessage, modalButtons, checkboxDiv];
288
+ const modalElems = [closeBtn, modalTitle, modalMessage, checkboxDiv, modalButtons ];
281
289
  modalElems.forEach((elem) => { modal.append(elem); });
282
290
  modal.style.width = `${ width || 458 }px`;
283
291
  modalContainer.append(modal); document.body.append(modalContainer);
@@ -942,7 +950,12 @@ const chatgpt = {
942
950
  getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
943
951
 
944
952
  getNewChatButton() {
945
- return document.querySelector('button[data-testid*=new-chat-button], button:has([d^="M15.6729"])'); },
953
+ return document.querySelector(
954
+ 'button[data-testid*=new-chat-button],' // sidebar button (when logged in)
955
+ + 'button:has([d^="M3.06957"]),' // Cycle Arrows icon (Temp chat mode)
956
+ + 'button:has([d^="M15.6729"])' // Pencil icon (recorded chat mode)
957
+ )
958
+ },
946
959
 
947
960
  getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
948
961
  getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
@@ -978,14 +991,14 @@ const chatgpt = {
978
991
 
979
992
  history: {
980
993
  async isLoaded(timeout = null) {
981
- const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
994
+ const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null
982
995
  const isLoadedPromise = new Promise(resolve => {
983
- if (document.querySelector('nav')) resolve(true);
996
+ if (document.querySelector('nav')) resolve(true)
984
997
  else new MutationObserver((_, obs) => {
985
- if (document.querySelector('nav')) { obs.disconnect(); resolve(true); }
986
- }).observe(document.body, { childList: true, subtree: true });
987
- });
988
- return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise );
998
+ if (document.querySelector('nav')) { obs.disconnect() ; resolve(true) }
999
+ }).observe(document.documentElement, { childList: true, subtree: true })
1000
+ })
1001
+ return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise )
989
1002
  }
990
1003
  },
991
1004
 
@@ -1146,14 +1159,14 @@ const chatgpt = {
1146
1159
  },
1147
1160
 
1148
1161
  async isLoaded(timeout = null) {
1149
- const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
1162
+ const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null
1150
1163
  const isLoadedPromise = new Promise(resolve => {
1151
- if (chatgpt.getNewChatBtn()) resolve(true);
1164
+ if (chatgpt.getNewChatBtn()) resolve(true)
1152
1165
  else new MutationObserver((_, obs) => {
1153
- if (chatgpt.getNewChatBtn()) { obs.disconnect(); resolve(true); }
1154
- }).observe(document.body, { childList: true, subtree: true });
1155
- });
1156
- return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise );
1166
+ if (chatgpt.getNewChatBtn()) { obs.disconnect() ; resolve(true) }
1167
+ }).observe(document.documentElement, { childList: true, subtree: true })
1168
+ })
1169
+ return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise )
1157
1170
  },
1158
1171
 
1159
1172
  isLightMode() { return document.documentElement.classList.toString().includes('light'); },
@@ -1309,13 +1322,18 @@ const chatgpt = {
1309
1322
  + 'font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC",'
1310
1323
  + '"Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif ;'
1311
1324
  + '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
1312
- + 'background-color: black ; padding: 10px 13px 10px 18px ; border-radius: 11px ; border: 1px solid #f5f5f7 ;' // bubble style
1325
+ + 'background-color: black ; padding: 10px 13px 10px 18px ;' // bubble style
1326
+ + 'border-radius: 11px ; border: 1px solid #f5f5f7 ;'
1313
1327
  + 'opacity: 0 ; position: fixed ; z-index: 9999 ; font-size: 1.8rem ; color: white ;' // visibility
1314
1328
  + 'user-select: none ; -webkit-user-select: none ; -moz-user-select: none ; -o-user-select: none ;'
1315
1329
  + '-ms-user-select: none ;'
1316
- + `transform: translateX(${ !notificationDiv.isRight ? '-' : '' }35px) ;` // init off-screen for transition fx
1317
- + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(?:shadow|on)\b/i.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1318
- + '.notif-close-btn { cursor: pointer ; float: right ; position: relative ; right: -4px ; margin-left: -3px ;'
1330
+ + `transform: translateX(${ // init off-screen for transition fx
1331
+ !notificationDiv.isRight ? '-' : '' }35px) ;`
1332
+ + ( shadow ? `--shadow: -8px 13px 25px 0 ${ /\b(?:shadow|on)\b/i.test(shadow) ? 'gray' : shadow };
1333
+ box-shadow: var(--shadow) ; -webkit-box-shadow: var(--shadow) ; -moz-box-shadow: var(--shadow)`
1334
+ : '' ) + '}'
1335
+ + `.notif-close-btn {
1336
+ cursor: pointer ; float: right ; position: relative ; right: -4px ; margin-left: -3px ;`
1319
1337
  + 'display: grid }' // top-align for non-OpenAI sites
1320
1338
  + '@keyframes notif-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }' // transition out keyframes
1321
1339
  + '15% { opacity: 0.35 ; transform: rotateX(-27deg) scale(1.05) }'
@@ -1853,15 +1871,15 @@ const chatgpt = {
1853
1871
  },
1854
1872
 
1855
1873
  async isLoaded(timeout = 5000) {
1856
- await chatgpt.isLoaded();
1857
- const timeoutPromise = new Promise(resolve => setTimeout(() => { resolve(false); }, timeout));
1874
+ await chatgpt.isLoaded()
1875
+ const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(false), timeout))
1858
1876
  const isLoadedPromise = new Promise(resolve => {
1859
- if (chatgpt.getNewChatLink()) resolve(true);
1877
+ if (chatgpt.getNewChatLink()) resolve(true)
1860
1878
  else new MutationObserver((_, obs) => {
1861
- if (chatgpt.getNewChatLink()) { obs.disconnect(); resolve(true); }
1862
- }).observe(document.body, { childList: true, subtree: true });
1863
- });
1864
- return await Promise.race([isLoadedPromise, timeoutPromise]);
1879
+ if (chatgpt.getNewChatLink()) { obs.disconnect() ; resolve(true) }
1880
+ }).observe(document.documentElement, { childList: true, subtree: true })
1881
+ })
1882
+ return await Promise.race([isLoadedPromise, timeoutPromise])
1865
1883
  }
1866
1884
  },
1867
1885
 
@@ -1879,7 +1897,7 @@ const chatgpt = {
1879
1897
  return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
1880
1898
  },
1881
1899
 
1882
- speak(msg, { voice = 2, pitch = 2, speed = 1.1, onend } = {} ) { // eslint-disable-line no-unused-vars
1900
+ speak(msg, { voice = 2, pitch = 2, speed = 1.1, onend } = {} ) {
1883
1901
  // Example call: chatgpt.speak(await chatgpt.getLastResponse(), { voice: 1, pitch: 2, speed: 3 })
1884
1902
  // - voice = index of voices available on user device
1885
1903
  // - pitch = float for pitch of speech from 0 to 2
@@ -1900,8 +1918,8 @@ const chatgpt = {
1900
1918
  }
1901
1919
 
1902
1920
  try { // to speak msg
1903
- const utterance = new SpeechSynthesisUtterance()
1904
- Object.assign(utterance, { text: msg, ...arguments[1], voice: speechSynthesis.getVoices()[voice] })
1921
+ const utterance = new SpeechSynthesisUtterance(), voices = speechSynthesis.getVoices()
1922
+ Object.assign(utterance, { text: msg, voice: voices[voice], pitch: pitch, speed: speed, onend: onend })
1905
1923
  speechSynthesis.speak(utterance)
1906
1924
  } catch (err) { console.error(err) }
1907
1925
  },
@@ -1,12 +1,11 @@
1
- window.dom = {
1
+ // Copyright © 2023–2025 Adam Lui (https://github.com/adamlui) under the MIT license
2
+ // Source: https://github.com/adamlui/ai-web-extensions/blob/main/assets/lib/dom.js/src/dom.js
2
3
 
3
- imports: {
4
- import(deps) { // { config, env }
5
- for (const depName in deps) this[depName] = deps[depName] }
6
- },
4
+ window.dom = {
5
+ import(deps) { Object.assign(this.imports = this.imports || {}, deps) },
7
6
 
8
7
  addRisingParticles(targetNode, { lightScheme = 'gray', darkScheme = 'white' } = {}) {
9
- // Requires https://assets.aiwebextensions.com/styles/rising-particles/dist/<lightScheme|darkScheme>.min.css
8
+ // * Requires https://assets.aiwebextensions.com/styles/rising-particles/dist/<lightScheme>.min.css
10
9
 
11
10
  if (targetNode.querySelector('[id*=particles]')) return
12
11
  const particlesDivsWrapper = document.createElement('div')
@@ -40,13 +39,14 @@ window.dom = {
40
39
  return elem
41
40
  },
42
41
 
43
- style(content) {
42
+ style(content, attrs = {}) {
44
43
  const style = document.createElement('style')
44
+ for (const attr in attrs) style.setAttribute(attr, attrs[attr])
45
45
  if (content) style.innerText = content
46
46
  return style
47
47
  },
48
48
 
49
- svgElem(type, attrs) {
49
+ svgElem(type, attrs = {}) {
50
50
  const elem = document.createElementNS('http://www.w3.org/2000/svg', type)
51
51
  for (const attr in attrs) elem.setAttributeNS(null, attr, attrs[attr])
52
52
  return elem
@@ -60,17 +60,39 @@ window.dom = {
60
60
  },
61
61
 
62
62
  get: {
63
- computedWidth(...elems) { // including margins
64
- let totalWidth = 0
65
- elems.map(arg => arg instanceof NodeList ? [...arg] : arg).flat().forEach(elem => {
66
- if (!(elem instanceof Element)) return
63
+
64
+ computedSize(elems, { dimension } = {}) { // total width/height of elems (including margins)
65
+ // * Returns { width: totalWidth, height: totalHeight } if no dimension passed
66
+ // * Returns float if { dimension: 'width' | 'height' } passed
67
+
68
+ // Validate args
69
+ elems = elems instanceof NodeList ? [...elems] : [].concat(elems)
70
+ elems.forEach(elem => { if (!(elem instanceof Node))
71
+ throw new Error(`Invalid elem: Element "${JSON.stringify(elem)}" is not a valid DOM node`) })
72
+ const validDimensions = ['width', 'height'], dimensionsToCompute = [].concat(dimension || validDimensions)
73
+ dimensionsToCompute.forEach(dimension => { if (!validDimensions.includes(dimension))
74
+ throw new Error('Invalid dimension: Use \'width\' or \'height\'') })
75
+
76
+ // Compute dimensions
77
+ const computedDimensions = { width: 0, height: 0 }
78
+ elems.forEach(elem => {
67
79
  const elemStyle = getComputedStyle(elem) ; if (elemStyle.display == 'none') return
68
- totalWidth += elem.getBoundingClientRect().width + parseFloat(elemStyle.marginLeft)
69
- + parseFloat(elemStyle.marginRight)
80
+ if (dimensionsToCompute.includes('width'))
81
+ computedDimensions.width += elem.getBoundingClientRect().width
82
+ + parseFloat(elemStyle.marginLeft) + parseFloat(elemStyle.marginRight)
83
+ if (dimensionsToCompute.includes('height'))
84
+ computedDimensions.height += elem.getBoundingClientRect().height
85
+ + parseFloat(elemStyle.marginTop) + parseFloat(elemStyle.marginBottom)
70
86
  })
71
- return totalWidth
87
+
88
+ // Return computed dimensions
89
+ return dimensionsToCompute.length > 1 ? computedDimensions // obj w/ width/height
90
+ : computedDimensions[dimensionsToCompute[0]] // single total val
72
91
  },
73
92
 
93
+ computedHeight(elems) { return this.computedSize(elems, { dimension: 'height' }) }, // including margins
94
+ computedWidth(elems) { return this.computedSize(elems, { dimension: 'width' }) }, // including margins
95
+
74
96
  loadedElem(selector, timeout = null) {
75
97
  const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(null), timeout)) : null
76
98
  const isLoadedPromise = new Promise(resolve => {
@@ -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.2.1",
6
+ "version": "2025.2.11",
7
7
  "author": "KudoAI",
8
8
  "homepage_url": "https://github.com/KudoAI/chatgpt.js-chrome-starter",
9
9
  "icons": {
@@ -10,7 +10,7 @@
10
10
 
11
11
  // Import APP data
12
12
  const { app } = await chrome.storage.sync.get('app')
13
- icons.imports.import({ app }) // for src's using app.urls.assetHost
13
+ icons.import({ app }) // for srcs using app.urls.assetHost
14
14
 
15
15
  // Define FUNCTIONS
16
16
 
@@ -3,12 +3,12 @@
3
3
  // @description A Greasemonkey template to start using chatgpt.js like a boss
4
4
  // @author chatgpt.js
5
5
  // @namespace https://chatgpt.js.org
6
- // @version 2025.2.1
6
+ // @version 2025.2.11
7
7
  // @license MIT
8
8
  // @icon https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon48.png
9
9
  // @icon64 https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon64.png
10
10
  // @match *://chatgpt.com/*
11
- // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.6.0/dist/chatgpt.min.js
11
+ // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.6.2/dist/chatgpt.min.js
12
12
  // @grant GM_getValue
13
13
  // @grant GM_setValue
14
14
  // @noframes