@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.
- package/LICENSE.md +26 -0
- package/README.md +16 -12
- package/chatgpt.js +88 -64
- package/dist/chatgpt.min.js +27 -21
- package/docs/README.md +17 -12
- package/docs/USERGUIDE.md +14 -12
- package/package.json +21 -16
- package/starters/chrome/extension/components/icons.js +28 -22
- package/starters/chrome/extension/components/modals.js +14 -12
- package/starters/chrome/extension/content.js +9 -18
- package/starters/chrome/extension/icons/faded/icon128.png +0 -0
- package/starters/chrome/extension/icons/faded/icon16.png +0 -0
- package/starters/chrome/extension/icons/faded/icon32.png +0 -0
- package/starters/chrome/extension/icons/faded/icon64.png +0 -0
- package/starters/chrome/extension/icons/icon128.png +0 -0
- package/starters/chrome/extension/icons/icon16.png +0 -0
- package/starters/chrome/extension/icons/icon32.png +0 -0
- package/starters/chrome/extension/icons/icon64.png +0 -0
- package/starters/chrome/extension/lib/chatgpt.js +88 -64
- package/starters/chrome/extension/lib/dom.js +17 -16
- package/starters/chrome/extension/lib/settings.js +11 -4
- package/starters/chrome/extension/lib/ui.js +6 -0
- package/starters/chrome/extension/manifest.json +4 -4
- package/starters/chrome/extension/popup/controller.js +197 -65
- package/starters/chrome/extension/popup/index.html +3 -3
- package/starters/chrome/extension/popup/style.css +88 -23
- package/starters/chrome/extension/service-worker.js +3 -5
- package/starters/greasemonkey/chatgpt.js-greasemonkey-starter.user.js +4 -4
|
@@ -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=
|
|
29
|
-
deepResearch: 'button[data-testid=
|
|
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=
|
|
38
|
-
reason: 'button[data-testid=
|
|
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
|
|
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: '
|
|
52
|
+
header: 'header#page-header',
|
|
53
53
|
links: { newChat: 'nav a[href="/"]', sidebarItem: 'nav a' },
|
|
54
|
-
sidebar: 'div
|
|
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${
|
|
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 '${
|
|
77
|
+
return reject(`🤖 chatgpt.js >> Persona '${persona}' was not found!`)
|
|
78
78
|
chatgpt.send(selectedPrompt.prompt, 'click')
|
|
79
|
-
console.info(`Loading ${
|
|
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
|
|
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'
|
|
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 = ''
|
|
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.
|
|
184
|
+
modalStyle.textContent = ( // update prev/new style contents
|
|
184
185
|
`.chatgpt-modal { /* vars */
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
+
-o-user-select: none ; -ms-user-select: none ;
|
|
218
219
|
transition: var(--transition) ; /* for fade-in + move-in */
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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.
|
|
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 >> [${
|
|
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_${
|
|
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 ${
|
|
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="${
|
|
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 +
|
|
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 +
|
|
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
|
-
&&
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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.
|
|
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 =
|
|
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.
|
|
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
|
-
//
|
|
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] = `${
|
|
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 ${
|
|
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${
|
|
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 ? '' : `${
|
|
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 [${
|
|
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 ${
|
|
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 ${
|
|
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', `${
|
|
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', `${
|
|
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 '${
|
|
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 ${
|
|
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' )
|
|
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 =
|
|
19
|
-
: `${(
|
|
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.
|
|
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
|
|
98
|
-
|
|
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)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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,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.
|
|
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": [
|
|
10
|
+
"permissions": ["activeTab", "storage"],
|
|
11
11
|
"action": { "default_popup": "popup/index.html" },
|
|
12
|
-
"web_accessible_resources": [{ "matches": [
|
|
13
|
-
"content_scripts": [{ "matches": [
|
|
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
|
}
|