@ash-qw/dsh-theme-prts 0.1.94 → 0.1.95
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/CHANGELOG.md +7 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +12 -2482
- package/package.json +2 -2
- package/src/client/composer-glass-adapter.js +6 -5
- package/src/client/index.js +1 -11
- package/src/client/operations-shell-v2.js +0 -4
- package/src/client/preferences.js +1 -35
- package/src/client/theme-settings-workbench.js +3 -234
- package/src/styles/prts.css +2 -61
- package/src/styles/theme-settings-workbench.css +1 -174
- package/src/client/conversation-scale-adapter.js +0 -2347
- package/src/styles/conversation-scale.css +0 -471
|
@@ -1,2347 +0,0 @@
|
|
|
1
|
-
import { CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT, CONVERSATION_SCALE_FOCUS_CONTRAST_MAX, CONVERSATION_SCALE_FOCUS_CONTRAST_MIN } from './preferences.js'
|
|
2
|
-
|
|
3
|
-
export const CONVERSATION_USER_TURN_SELECTOR = [
|
|
4
|
-
'[data-chat-flow-kind="user"]',
|
|
5
|
-
'[data-chat-flow-kind="user-step"]',
|
|
6
|
-
'[data-message-role="user"]',
|
|
7
|
-
].join(',')
|
|
8
|
-
|
|
9
|
-
export const CONVERSATION_SCALE_SPACING = 8
|
|
10
|
-
export const CONVERSATION_SCALE_BASE_LENGTHS = Object.freeze([24, 18, 14, 10, 6])
|
|
11
|
-
export const CONVERSATION_SCALE_CONTRAST_DELTAS = Object.freeze([5, -3, -4, -4, -3])
|
|
12
|
-
|
|
13
|
-
export function resolveConversationScaleLengths(value = CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT) {
|
|
14
|
-
const numeric = Number(value)
|
|
15
|
-
const contrast = Number.isFinite(numeric)
|
|
16
|
-
? Math.min(CONVERSATION_SCALE_FOCUS_CONTRAST_MAX, Math.max(CONVERSATION_SCALE_FOCUS_CONTRAST_MIN, numeric))
|
|
17
|
-
: CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT
|
|
18
|
-
const ratio = contrast / CONVERSATION_SCALE_FOCUS_CONTRAST_MAX
|
|
19
|
-
return CONVERSATION_SCALE_BASE_LENGTHS.map((length, index) =>
|
|
20
|
-
Math.round(length + CONVERSATION_SCALE_CONTRAST_DELTAS[index] * ratio))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const CONVERSATION_SCALE_LENGTHS = Object.freeze(resolveConversationScaleLengths())
|
|
24
|
-
export const CONVERSATION_SCALE_WAVE_DURATION = 140
|
|
25
|
-
|
|
26
|
-
export const CONVERSATION_JUMP_BOTTOM_THRESHOLD = 25
|
|
27
|
-
export const CONVERSATION_JUMP_SMOOTH_VIEWPORT_LIMIT = 1.5
|
|
28
|
-
export const CONVERSATION_JUMP_SETTLE_MAX_DISTANCE = 88
|
|
29
|
-
export const CONVERSATION_JUMP_SETTLE_VIEWPORT_RATIO = .12
|
|
30
|
-
export const CONVERSATION_JUMP_SETTLE_DURATION = 180
|
|
31
|
-
export const CONVERSATION_JUMP_VERIFY_DURATION = 900
|
|
32
|
-
export const CONVERSATION_HISTORY_PAGE_LIMIT = 10
|
|
33
|
-
export const CONVERSATION_HISTORY_TIME_LIMIT = 8000
|
|
34
|
-
|
|
35
|
-
const ASSISTANT_SELECTOR = '[data-chat-flow-kind="assistant-step"], [data-message-role="assistant"]'
|
|
36
|
-
const FORMAL_ANSWER_SELECTOR = '[data-prts-ai-surface], [data-markdown], [data-slot*="markdown" i], .markdown-body'
|
|
37
|
-
const AUXILIARY_SELECTOR = '[data-chat-flow-kind*="reason" i], [data-chat-flow-kind*="tool" i], [data-slot*="reason" i], [data-slot*="tool" i], [data-role*="reason" i], [data-role*="tool" i]'
|
|
38
|
-
const STREAMING_SELECTOR = '[aria-busy="true"], [data-streaming], [data-status*="stream" i], [data-state*="stream" i]'
|
|
39
|
-
const CONVERSATION_THIRD_PARTY_OVERLAY_SELECTOR = [
|
|
40
|
-
'[data-shell-overlay] [data-plugin]:not([data-plugin="dsh-theme-prts"])',
|
|
41
|
-
'[data-slot="shell.overlay"] [data-plugin]:not([data-plugin="dsh-theme-prts"])',
|
|
42
|
-
].join(', ')
|
|
43
|
-
const CONVERSATION_BLOCKING_OVERLAY_SELECTOR = [
|
|
44
|
-
'[role="dialog"]',
|
|
45
|
-
'[aria-modal="true"]',
|
|
46
|
-
'dialog[open]',
|
|
47
|
-
'[data-prts-floating-glass="dialog"]',
|
|
48
|
-
'[data-prts-floating-scrim]',
|
|
49
|
-
'[data-slot*="dialog" i]',
|
|
50
|
-
'[data-slot*="modal" i]',
|
|
51
|
-
'[class*="_dialog"]',
|
|
52
|
-
'[class*="_modal"]',
|
|
53
|
-
'[class*="_scrim"]',
|
|
54
|
-
'[class*="_backdrop"]',
|
|
55
|
-
].join(', ')
|
|
56
|
-
const CONVERSATION_OVERLAY_SELECTOR = [
|
|
57
|
-
CONVERSATION_THIRD_PARTY_OVERLAY_SELECTOR,
|
|
58
|
-
'[role="dialog"]',
|
|
59
|
-
'[role="menu"]',
|
|
60
|
-
'[role="listbox"]',
|
|
61
|
-
'[popover]',
|
|
62
|
-
'[data-prts-floating-glass]',
|
|
63
|
-
'[aria-modal="true"]',
|
|
64
|
-
'dialog[open]',
|
|
65
|
-
'[data-slot*="dialog" i]',
|
|
66
|
-
'[data-slot*="modal" i]',
|
|
67
|
-
'[data-prts-floating-scrim]',
|
|
68
|
-
'[class*="_dialog"]',
|
|
69
|
-
'[class*="_modal"]',
|
|
70
|
-
'[class*="_scrim"]',
|
|
71
|
-
'[class*="_backdrop"]',
|
|
72
|
-
].join(', ')
|
|
73
|
-
const CONVERSATION_PORTAL_ROOT_SELECTOR = [
|
|
74
|
-
'[role="presentation"]',
|
|
75
|
-
'[class*="_portal_"]',
|
|
76
|
-
'[data-floating-ui-portal]',
|
|
77
|
-
'[data-radix-portal]',
|
|
78
|
-
].join(', ')
|
|
79
|
-
const PREVIEW_REMOVE_SELECTOR = [
|
|
80
|
-
'[hidden]',
|
|
81
|
-
'[aria-hidden="true"]',
|
|
82
|
-
'button',
|
|
83
|
-
'[role="toolbar"]',
|
|
84
|
-
'[data-slot*="toolbar" i]',
|
|
85
|
-
'[data-slot*="action" i]',
|
|
86
|
-
'[data-slot*="sentinel" i]',
|
|
87
|
-
'script',
|
|
88
|
-
'style',
|
|
89
|
-
].join(',')
|
|
90
|
-
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
|
|
91
|
-
const MINIMUM_TURNS = 2
|
|
92
|
-
export const CONVERSATION_SCALE_WIDTH = 32
|
|
93
|
-
export const CONVERSATION_SCALE_CONTENT_GAP = 12
|
|
94
|
-
export const CONVERSATION_SCALE_PREVIEW_WIDTH = 360
|
|
95
|
-
export const CONVERSATION_SCALE_PREVIEW_GAP = 8
|
|
96
|
-
export const CONVERSATION_SCALE_RIGHT_GAP = 12
|
|
97
|
-
export const CONVERSATION_SCALE_HYSTERESIS = 16
|
|
98
|
-
export const CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE = 96
|
|
99
|
-
const SCALE_WIDTH = CONVERSATION_SCALE_WIDTH
|
|
100
|
-
const PREVIEW_WIDTH = CONVERSATION_SCALE_PREVIEW_WIDTH
|
|
101
|
-
const PREVIEW_GAP = CONVERSATION_SCALE_PREVIEW_GAP
|
|
102
|
-
const PREVIEW_FALLBACK_HEIGHT = 128
|
|
103
|
-
const PREVIEW_DELAY = 100
|
|
104
|
-
const HISTORY_TRIGGER_HEIGHT = CONVERSATION_SCALE_SPACING
|
|
105
|
-
const PREVIEW_REFRESH_INTERVAL = 100
|
|
106
|
-
const PREVIEW_TOUCH_DURATION = 1200
|
|
107
|
-
const SNAP_DELAY = 110
|
|
108
|
-
const TOUCH_WAVE_DURATION = 620
|
|
109
|
-
const HISTORY_PAGE_SETTLE_LIMIT = 4000
|
|
110
|
-
const GEOMETRY_SETTLE_FRAME_LIMIT = 8
|
|
111
|
-
const GEOMETRY_SETTLE_STABLE_FRAMES = 2
|
|
112
|
-
let scaleInstanceSeed = 0
|
|
113
|
-
|
|
114
|
-
function clamp(value, minimum, maximum) {
|
|
115
|
-
return Math.min(maximum, Math.max(minimum, value))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function resolveConversationScaleGeometry({
|
|
119
|
-
operationWidth,
|
|
120
|
-
contentLeft,
|
|
121
|
-
maximumDistance = CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE,
|
|
122
|
-
previouslyHidden = false,
|
|
123
|
-
} = {}) {
|
|
124
|
-
const width = Number(operationWidth)
|
|
125
|
-
const content = Number(contentLeft)
|
|
126
|
-
const maximum = Number(maximumDistance)
|
|
127
|
-
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(content) || content <= 0) {
|
|
128
|
-
return { visible: false, reason: 'unmeasured' }
|
|
129
|
-
}
|
|
130
|
-
const recovery = previouslyHidden ? CONVERSATION_SCALE_HYSTERESIS : 0
|
|
131
|
-
const minimumCorridor = CONVERSATION_SCALE_WIDTH + CONVERSATION_SCALE_CONTENT_GAP * 2 + recovery
|
|
132
|
-
if (content < minimumCorridor) return { visible: false, reason: 'corridor' }
|
|
133
|
-
const configuredLeft = clamp(
|
|
134
|
-
Number.isFinite(maximum) ? maximum : CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE,
|
|
135
|
-
CONVERSATION_SCALE_CONTENT_GAP,
|
|
136
|
-
Math.max(CONVERSATION_SCALE_CONTENT_GAP, width - CONVERSATION_SCALE_WIDTH - CONVERSATION_SCALE_RIGHT_GAP),
|
|
137
|
-
)
|
|
138
|
-
const centered = configuredLeft + CONVERSATION_SCALE_WIDTH + CONVERSATION_SCALE_CONTENT_GAP > content
|
|
139
|
-
const left = centered ? (content - CONVERSATION_SCALE_WIDTH) / 2 : configuredLeft
|
|
140
|
-
const previewRight = left
|
|
141
|
-
+ CONVERSATION_SCALE_WIDTH
|
|
142
|
-
+ CONVERSATION_SCALE_PREVIEW_GAP
|
|
143
|
-
+ CONVERSATION_SCALE_PREVIEW_WIDTH
|
|
144
|
-
+ CONVERSATION_SCALE_RIGHT_GAP
|
|
145
|
-
+ recovery
|
|
146
|
-
if (previewRight > width) return { visible: false, reason: 'preview', left, centered }
|
|
147
|
-
return { visible: true, reason: 'ready', left, centered }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
export function resolveConversationScaleCalibrationState({
|
|
152
|
-
operationWidth,
|
|
153
|
-
contentLeft,
|
|
154
|
-
maximumDistance = CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE,
|
|
155
|
-
turnCount = 0,
|
|
156
|
-
drawerMode,
|
|
157
|
-
viewportWidth,
|
|
158
|
-
previouslyHidden = false,
|
|
159
|
-
} = {}) {
|
|
160
|
-
const numeric = Number(maximumDistance)
|
|
161
|
-
const normalizedMaximum = Number.isFinite(numeric)
|
|
162
|
-
? clamp(Math.round(numeric / 8) * 8, 16, 240)
|
|
163
|
-
: CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE
|
|
164
|
-
const resolution = drawerMode === 'overlay'
|
|
165
|
-
? { visible: false, reason: 'drawer' }
|
|
166
|
-
: Number(viewportWidth) <= 640
|
|
167
|
-
? { visible: false, reason: 'phone' }
|
|
168
|
-
: resolveConversationScaleGeometry({
|
|
169
|
-
operationWidth,
|
|
170
|
-
contentLeft,
|
|
171
|
-
maximumDistance: normalizedMaximum,
|
|
172
|
-
previouslyHidden,
|
|
173
|
-
})
|
|
174
|
-
if (!resolution.visible) return {
|
|
175
|
-
...resolution,
|
|
176
|
-
mode: 'hidden',
|
|
177
|
-
maximumDistance: normalizedMaximum,
|
|
178
|
-
}
|
|
179
|
-
if (Number(turnCount) < MINIMUM_TURNS) return {
|
|
180
|
-
visible: false,
|
|
181
|
-
reason: 'turns',
|
|
182
|
-
mode: 'hidden',
|
|
183
|
-
maximumDistance: normalizedMaximum,
|
|
184
|
-
left: resolution.left,
|
|
185
|
-
centered: resolution.centered,
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
...resolution,
|
|
189
|
-
mode: resolution.centered ? 'centered' : 'configured',
|
|
190
|
-
maximumDistance: normalizedMaximum,
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function visible(node) {
|
|
195
|
-
return node && !node.hidden && node.getAttribute('aria-hidden') !== 'true'
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function follows(first, second) {
|
|
199
|
-
return Boolean(first?.compareDocumentPosition?.(second) & 4)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function truncateText(value, limit) {
|
|
203
|
-
const characters = Array.from(value)
|
|
204
|
-
return characters.length > limit ? `${characters.slice(0, limit).join('')}…` : value
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function attachmentSummary(node) {
|
|
208
|
-
const named = node?.querySelector?.('[data-file-name], [download], img[alt]')
|
|
209
|
-
const name = named?.getAttribute('data-file-name')
|
|
210
|
-
|| named?.getAttribute('download')
|
|
211
|
-
|| named?.getAttribute('alt')
|
|
212
|
-
if (name?.trim()) return `${named?.matches('img') ? '图片' : '附件'} · ${name.trim()}`
|
|
213
|
-
if (node?.querySelector?.('img, [data-file], [data-attachment], input[type="file"]')) return '附件消息'
|
|
214
|
-
return ''
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function sanitizeConversationPreviewNode(node, limit = 360) {
|
|
218
|
-
if (!node) return ''
|
|
219
|
-
const clone = node.cloneNode(true)
|
|
220
|
-
for (const removable of clone.querySelectorAll?.(PREVIEW_REMOVE_SELECTOR) || []) removable.remove()
|
|
221
|
-
for (const block of clone.querySelectorAll?.('pre') || []) {
|
|
222
|
-
const firstLine = block.textContent?.split(/\r?\n/).map(line => line.trim()).find(Boolean) || ''
|
|
223
|
-
block.textContent = firstLine ? `代码:${firstLine}` : '代码'
|
|
224
|
-
}
|
|
225
|
-
const text = clone.textContent?.replace(/\s+/g, ' ').trim() || attachmentSummary(node)
|
|
226
|
-
return truncateText(text, limit)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function collectConversationTurns(scroller) {
|
|
230
|
-
if (!scroller) return []
|
|
231
|
-
return Array.from(scroller.querySelectorAll(CONVERSATION_USER_TURN_SELECTOR)).filter(node => {
|
|
232
|
-
if (!visible(node) || node.closest('[data-prts-conversation-scale], [data-prts-conversation-preview]')) return false
|
|
233
|
-
const parent = node.parentElement?.closest?.(CONVERSATION_USER_TURN_SELECTOR)
|
|
234
|
-
return !parent || !scroller.contains(parent)
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function hasClassSuffix(node, suffix) {
|
|
239
|
-
return [...(node?.classList ?? [])].some(token => token.endsWith(suffix))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function findConversationOlderButton(root) {
|
|
243
|
-
for (const button of root?.querySelectorAll?.('button') ?? []) {
|
|
244
|
-
for (let current = button.parentElement; current && root.contains(current); current = current.parentElement) {
|
|
245
|
-
if (hasClassSuffix(current, '_older')) return button
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return null
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export function resolveConversationHistorySnapshot(sessions) {
|
|
252
|
-
try {
|
|
253
|
-
const listSnapshot = sessions?.list?.getSnapshot?.()
|
|
254
|
-
const current = listSnapshot?.current
|
|
255
|
-
?? listSnapshot?.currentId
|
|
256
|
-
?? listSnapshot?.currentSessionId
|
|
257
|
-
if (current === undefined || current === null) return {}
|
|
258
|
-
const binding = sessions?.binding?.(current)
|
|
259
|
-
const session = binding?.session
|
|
260
|
-
const snapshot = session?.getSnapshot?.() ?? binding?.getSnapshot?.()
|
|
261
|
-
return { key: current, hasMore: snapshot?.hasMore === true, loading: snapshot?.loadingOlder === true }
|
|
262
|
-
} catch {
|
|
263
|
-
return {}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function collectAssistantCandidates(scroller, turn, nextTurn) {
|
|
268
|
-
return Array.from(scroller?.querySelectorAll?.(ASSISTANT_SELECTOR) || []).filter(node => {
|
|
269
|
-
if (!visible(node)) return false
|
|
270
|
-
const parent = node.parentElement?.closest?.(ASSISTANT_SELECTOR)
|
|
271
|
-
if (parent && scroller.contains(parent)) return false
|
|
272
|
-
if (!follows(turn, node)) return false
|
|
273
|
-
return !nextTurn || follows(node, nextTurn)
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function formalBodies(candidate) {
|
|
278
|
-
const bodies = []
|
|
279
|
-
if (candidate.matches?.(FORMAL_ANSWER_SELECTOR)) bodies.push(candidate)
|
|
280
|
-
bodies.push(...candidate.querySelectorAll?.(FORMAL_ANSWER_SELECTOR) || [])
|
|
281
|
-
return bodies.filter(body => !body.closest?.(AUXILIARY_SELECTOR))
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function getConversationTurnPreview(scroller, turns, index) {
|
|
285
|
-
const turn = turns?.[index]
|
|
286
|
-
if (!turn) return { question: '', answer: '尚无有效回复', state: 'empty', answerSource: undefined }
|
|
287
|
-
const nextTurn = turns[index + 1]
|
|
288
|
-
const questionSource = turn.querySelector?.('[class$="_bubble"], [data-message-role="user"]') || turn
|
|
289
|
-
const question = sanitizeConversationPreviewNode(questionSource, 180) || attachmentSummary(turn) || '附件消息'
|
|
290
|
-
const candidates = collectAssistantCandidates(scroller, turn, nextTurn)
|
|
291
|
-
|
|
292
|
-
for (const candidate of candidates) {
|
|
293
|
-
for (const body of formalBodies(candidate)) {
|
|
294
|
-
const answer = sanitizeConversationPreviewNode(body, 420)
|
|
295
|
-
if (answer) return { question, answer, state: 'answer', answerSource: body }
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
for (const candidate of candidates) {
|
|
300
|
-
const answer = sanitizeConversationPreviewNode(candidate, 420)
|
|
301
|
-
if (answer) return { question, answer, state: 'fallback', answerSource: candidate }
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const streaming = candidates.some(candidate => candidate.matches?.(STREAMING_SELECTOR) || candidate.querySelector?.(STREAMING_SELECTOR))
|
|
305
|
-
return {
|
|
306
|
-
question,
|
|
307
|
-
answer: streaming ? '正在生成…' : '尚无有效回复',
|
|
308
|
-
state: streaming ? 'streaming' : 'empty',
|
|
309
|
-
answerSource: candidates[0],
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function getConversationScaleLayout(count, height, requestedOffset = 0) {
|
|
314
|
-
const safeCount = Math.max(0, Math.floor(Number(count) || 0))
|
|
315
|
-
const safeHeight = Math.max(1, Number(height) || 1)
|
|
316
|
-
const span = Math.max(0, safeCount - 1) * CONVERSATION_SCALE_SPACING
|
|
317
|
-
const usableHeight = Math.max(0, safeHeight - 1)
|
|
318
|
-
const maxOffset = Math.max(0, span - usableHeight)
|
|
319
|
-
const offset = clamp(Number(requestedOffset) || 0, 0, maxOffset)
|
|
320
|
-
const start = .5 + (maxOffset === 0 ? (usableHeight - span) / 2 : 0)
|
|
321
|
-
const positions = Array.from({ length: safeCount }, (_, index) =>
|
|
322
|
-
start + index * CONVERSATION_SCALE_SPACING - offset)
|
|
323
|
-
return { count: safeCount, height: safeHeight, span, maxOffset, offset, start, positions }
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function conversationScaleWaveLength(distance, lengths = CONVERSATION_SCALE_LENGTHS) {
|
|
327
|
-
const safeDistance = Math.abs(Number(distance) || 0)
|
|
328
|
-
const lower = Math.floor(safeDistance)
|
|
329
|
-
if (lower >= lengths.length - 1) return lengths.at(-1)
|
|
330
|
-
const progress = safeDistance - lower
|
|
331
|
-
const from = lengths[lower]
|
|
332
|
-
const to = lengths[lower + 1]
|
|
333
|
-
return from + (to - from) * progress
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function visibleConversationTickRange(layout, buffer = 2) {
|
|
337
|
-
if (!layout?.count) return { minimum: 0, maximum: -1 }
|
|
338
|
-
const padding = Math.max(0, Math.floor(Number(buffer) || 0))
|
|
339
|
-
const minimum = Math.max(
|
|
340
|
-
0,
|
|
341
|
-
Math.ceil((layout.offset - layout.start) / CONVERSATION_SCALE_SPACING) - padding,
|
|
342
|
-
)
|
|
343
|
-
const maximum = Math.min(
|
|
344
|
-
layout.count - 1,
|
|
345
|
-
Math.floor((layout.height - .5 + layout.offset - layout.start) / CONVERSATION_SCALE_SPACING) + padding,
|
|
346
|
-
)
|
|
347
|
-
return { minimum, maximum }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export function nearestVisibleConversationTick(layout, localY) {
|
|
351
|
-
if (!layout?.count) return -1
|
|
352
|
-
const minimum = Math.max(0, Math.ceil((layout.offset - layout.start) / CONVERSATION_SCALE_SPACING))
|
|
353
|
-
const maximum = Math.min(
|
|
354
|
-
layout.count - 1,
|
|
355
|
-
Math.floor((layout.height - .5 + layout.offset - layout.start) / CONVERSATION_SCALE_SPACING),
|
|
356
|
-
)
|
|
357
|
-
if (maximum < minimum) return -1
|
|
358
|
-
const raw = Math.round((Number(localY) + layout.offset - layout.start) / CONVERSATION_SCALE_SPACING)
|
|
359
|
-
return clamp(raw, minimum, maximum)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export function snapConversationScaleOffset(offset, maximum) {
|
|
363
|
-
return clamp(
|
|
364
|
-
Math.round((Number(offset) || 0) / CONVERSATION_SCALE_SPACING) * CONVERSATION_SCALE_SPACING,
|
|
365
|
-
0,
|
|
366
|
-
Math.max(0, Number(maximum) || 0),
|
|
367
|
-
)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function shouldFollowConversationScaleBottom(offset, maximum) {
|
|
371
|
-
return Math.max(0, Number(maximum) || 0) - (Number(offset) || 0) <= 1
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
export function calculateConversationJumpTop({
|
|
375
|
-
scrollTop,
|
|
376
|
-
scrollHeight,
|
|
377
|
-
clientHeight,
|
|
378
|
-
turnTop,
|
|
379
|
-
visibleTop,
|
|
380
|
-
visibleHeight,
|
|
381
|
-
}) {
|
|
382
|
-
const target = (Number(scrollTop) || 0)
|
|
383
|
-
+ (Number(turnTop) || 0)
|
|
384
|
-
- (Number(visibleTop) || 0)
|
|
385
|
-
- Math.max(0, Number(visibleHeight) || 0) * .28
|
|
386
|
-
return clamp(target, 0, Math.max(0, (Number(scrollHeight) || 0) - (Number(clientHeight) || 0)))
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export function resolveConversationJumpBehavior(distance, viewportHeight, prefersReducedMotion = false) {
|
|
390
|
-
if (prefersReducedMotion) return 'auto'
|
|
391
|
-
const height = Math.max(0, Number(viewportHeight) || 0)
|
|
392
|
-
const delta = Math.abs(Number(distance) || 0)
|
|
393
|
-
return height > 0 && delta <= height * CONVERSATION_JUMP_SMOOTH_VIEWPORT_LIMIT ? 'smooth' : 'auto'
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export function resolveConversationJumpSettle(distance, viewportHeight, prefersReducedMotion = false) {
|
|
397
|
-
if (prefersReducedMotion || resolveConversationJumpBehavior(distance, viewportHeight) === 'smooth') {
|
|
398
|
-
return { offset: 0, duration: 0 }
|
|
399
|
-
}
|
|
400
|
-
const height = Math.max(0, Number(viewportHeight) || 0)
|
|
401
|
-
const delta = Number(distance) || 0
|
|
402
|
-
if (height <= 0 || Math.abs(delta) <= 2) return { offset: 0, duration: 0 }
|
|
403
|
-
const offset = Math.sign(delta) * Math.min(
|
|
404
|
-
Math.abs(delta),
|
|
405
|
-
CONVERSATION_JUMP_SETTLE_MAX_DISTANCE,
|
|
406
|
-
height * CONVERSATION_JUMP_SETTLE_VIEWPORT_RATIO,
|
|
407
|
-
)
|
|
408
|
-
return { offset, duration: CONVERSATION_JUMP_SETTLE_DURATION }
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function scrollConversationTo(scroller, top, behavior) {
|
|
412
|
-
if (typeof scroller?.scrollTo === 'function') scroller.scrollTo({ top, behavior })
|
|
413
|
-
else if (scroller) scroller.scrollTop = top
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export async function performConversationJump({
|
|
417
|
-
scroller,
|
|
418
|
-
getTargetTop,
|
|
419
|
-
prefersReducedMotion = false,
|
|
420
|
-
requestFrame = callback => globalThis.requestAnimationFrame?.(callback) ?? globalThis.setTimeout?.(callback, 16),
|
|
421
|
-
now = () => globalThis.performance?.now?.() ?? Date.now(),
|
|
422
|
-
cancelled = () => false,
|
|
423
|
-
onFrame = () => {},
|
|
424
|
-
} = {}) {
|
|
425
|
-
if (!scroller || typeof getTargetTop !== 'function') return false
|
|
426
|
-
const nextFrame = () => new Promise(resolve => requestFrame(timestamp => {
|
|
427
|
-
resolve(Number.isFinite(timestamp) ? timestamp : now())
|
|
428
|
-
}))
|
|
429
|
-
const targetTop = () => {
|
|
430
|
-
const raw = getTargetTop()
|
|
431
|
-
if (raw === null || raw === undefined) return null
|
|
432
|
-
const value = Number(raw)
|
|
433
|
-
return Number.isFinite(value) ? value : null
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
let top = targetTop()
|
|
437
|
-
if (top === null) return false
|
|
438
|
-
const floor = Math.max(0, Number(scroller.scrollHeight) - Number(scroller.clientHeight))
|
|
439
|
-
const bottomDistance = floor - Number(scroller.scrollTop)
|
|
440
|
-
if (bottomDistance <= CONVERSATION_JUMP_BOTTOM_THRESHOLD && top < Number(scroller.scrollTop)) {
|
|
441
|
-
scroller.scrollTop = Math.max(0, Number(scroller.scrollTop) - 1)
|
|
442
|
-
await nextFrame()
|
|
443
|
-
if (cancelled()) return null
|
|
444
|
-
top = targetTop()
|
|
445
|
-
if (top === null) return false
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const distance = top - Number(scroller.scrollTop)
|
|
449
|
-
const behavior = resolveConversationJumpBehavior(distance, scroller.clientHeight, prefersReducedMotion)
|
|
450
|
-
const settle = resolveConversationJumpSettle(distance, scroller.clientHeight, prefersReducedMotion)
|
|
451
|
-
if (settle.duration > 0) {
|
|
452
|
-
const limit = () => Math.max(0, Number(scroller.scrollHeight) - Number(scroller.clientHeight))
|
|
453
|
-
const beforeTarget = clamp(top - settle.offset, 0, limit())
|
|
454
|
-
scrollConversationTo(scroller, beforeTarget, 'auto')
|
|
455
|
-
await nextFrame()
|
|
456
|
-
if (cancelled()) return null
|
|
457
|
-
const from = Number(scroller.scrollTop)
|
|
458
|
-
const startedAt = now()
|
|
459
|
-
let progress = 0
|
|
460
|
-
while (progress < 1) {
|
|
461
|
-
const timestamp = await nextFrame()
|
|
462
|
-
if (cancelled()) return null
|
|
463
|
-
progress = Math.min(1, (timestamp - startedAt) / settle.duration)
|
|
464
|
-
const eased = 1 - (1 - progress) ** 3
|
|
465
|
-
const liveTarget = targetTop()
|
|
466
|
-
if (liveTarget === null) return false
|
|
467
|
-
scroller.scrollTop = from + (liveTarget - from) * eased
|
|
468
|
-
onFrame()
|
|
469
|
-
}
|
|
470
|
-
} else {
|
|
471
|
-
scrollConversationTo(scroller, top, behavior)
|
|
472
|
-
onFrame()
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
let stableFrames = 0
|
|
476
|
-
let verificationFrames = 0
|
|
477
|
-
const verificationStartedAt = now()
|
|
478
|
-
while (now() - verificationStartedAt < CONVERSATION_JUMP_VERIFY_DURATION && verificationFrames < 72) {
|
|
479
|
-
await nextFrame()
|
|
480
|
-
verificationFrames += 1
|
|
481
|
-
if (cancelled()) return null
|
|
482
|
-
const liveTarget = targetTop()
|
|
483
|
-
if (liveTarget === null) return false
|
|
484
|
-
stableFrames = Math.abs(Number(scroller.scrollTop) - liveTarget) <= 2 ? stableFrames + 1 : 0
|
|
485
|
-
if (stableFrames >= 2) return true
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
top = targetTop()
|
|
489
|
-
if (top === null) return false
|
|
490
|
-
scrollConversationTo(scroller, top, 'auto')
|
|
491
|
-
await nextFrame()
|
|
492
|
-
await nextFrame()
|
|
493
|
-
if (cancelled()) return null
|
|
494
|
-
const correctedTarget = targetTop()
|
|
495
|
-
return correctedTarget !== null && Math.abs(Number(scroller.scrollTop) - correctedTarget) <= 2
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
export function buildConversationScalePaths(layout, interactionIndex = -1, animation, lengths = CONVERSATION_SCALE_LENGTHS) {
|
|
499
|
-
const gray = []
|
|
500
|
-
const yellow = []
|
|
501
|
-
const history = []
|
|
502
|
-
const hovering = interactionIndex >= 0 && interactionIndex < layout.count
|
|
503
|
-
const animatedCenter = Number.isFinite(animation?.center) ? animation.center : interactionIndex
|
|
504
|
-
const animatedIntensity = animation
|
|
505
|
-
? clamp(Number(animation.intensity) || 0, 0, 1)
|
|
506
|
-
: hovering ? 1 : 0
|
|
507
|
-
const restingLength = lengths.at(-1)
|
|
508
|
-
const { minimum, maximum } = visibleConversationTickRange(layout)
|
|
509
|
-
for (let index = minimum; index <= maximum; index += 1) {
|
|
510
|
-
const rawY = layout.start + index * CONVERSATION_SCALE_SPACING - layout.offset
|
|
511
|
-
if (rawY < .5 || rawY > layout.height - .5) continue
|
|
512
|
-
const y = Number(rawY.toFixed(2))
|
|
513
|
-
const waveLength = conversationScaleWaveLength(Math.abs(index - animatedCenter), lengths)
|
|
514
|
-
if (animation?.historyAvailable && index === 0) {
|
|
515
|
-
const selected = interactionIndex === 0
|
|
516
|
-
const intensity = selected ? animatedIntensity : 0
|
|
517
|
-
const restingFirstEnd = 2 + restingLength
|
|
518
|
-
const restingSecondStart = restingFirstEnd + 4
|
|
519
|
-
const restingSecondEnd = restingSecondStart + restingLength
|
|
520
|
-
const firstEnd = Number((
|
|
521
|
-
restingFirstEnd + (14 - restingFirstEnd) * intensity
|
|
522
|
-
).toFixed(2))
|
|
523
|
-
const secondStart = Number((
|
|
524
|
-
restingSecondStart + (16 - restingSecondStart) * intensity
|
|
525
|
-
).toFixed(2))
|
|
526
|
-
const secondEnd = Number((
|
|
527
|
-
restingSecondEnd + (30 - restingSecondEnd) * intensity
|
|
528
|
-
).toFixed(2))
|
|
529
|
-
history.push(`M2 ${y}H${firstEnd}M${secondStart} ${y}H${secondEnd}`)
|
|
530
|
-
continue
|
|
531
|
-
}
|
|
532
|
-
const length = restingLength + (waveLength - restingLength) * animatedIntensity
|
|
533
|
-
const command = `M2 ${y}H${Number((2 + length).toFixed(2))}`
|
|
534
|
-
const accented = hovering ? index === interactionIndex : index === layout.count - 1
|
|
535
|
-
;(accented ? yellow : gray).push(command)
|
|
536
|
-
}
|
|
537
|
-
return { gray: gray.join(''), yellow: yellow.join(''), history: history.join('') }
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function createPreview(document, id) {
|
|
541
|
-
const preview = document.createElement('aside')
|
|
542
|
-
preview.id = id
|
|
543
|
-
preview.setAttribute('data-prts-conversation-preview', '')
|
|
544
|
-
preview.setAttribute('data-prts-owned-conversation-preview', '')
|
|
545
|
-
preview.setAttribute('role', 'tooltip')
|
|
546
|
-
preview.setAttribute('aria-hidden', 'true')
|
|
547
|
-
const connector = document.createElement('span')
|
|
548
|
-
connector.setAttribute('data-prts-conversation-preview-connector', '')
|
|
549
|
-
connector.setAttribute('aria-hidden', 'true')
|
|
550
|
-
const body = document.createElement('div')
|
|
551
|
-
body.setAttribute('data-prts-conversation-preview-body', '')
|
|
552
|
-
const question = document.createElement('div')
|
|
553
|
-
question.setAttribute('data-prts-conversation-preview-row', 'question')
|
|
554
|
-
const questionLabel = document.createElement('b')
|
|
555
|
-
questionLabel.textContent = 'Q'
|
|
556
|
-
const questionText = document.createElement('span')
|
|
557
|
-
questionText.setAttribute('data-prts-conversation-preview-question', '')
|
|
558
|
-
question.append(questionLabel, questionText)
|
|
559
|
-
const answer = document.createElement('div')
|
|
560
|
-
answer.setAttribute('data-prts-conversation-preview-row', 'answer')
|
|
561
|
-
const answerLabel = document.createElement('b')
|
|
562
|
-
answerLabel.textContent = 'A'
|
|
563
|
-
const answerText = document.createElement('span')
|
|
564
|
-
answerText.setAttribute('data-prts-conversation-preview-answer', '')
|
|
565
|
-
answer.append(answerLabel, answerText)
|
|
566
|
-
body.append(question, answer)
|
|
567
|
-
preview.append(connector, body)
|
|
568
|
-
preview._prtsBody = body
|
|
569
|
-
preview._prtsQuestion = questionText
|
|
570
|
-
preview._prtsAnswer = answerText
|
|
571
|
-
return preview
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function createScale(document) {
|
|
575
|
-
const instance = ++scaleInstanceSeed
|
|
576
|
-
const maskId = `prts-conversation-scale-fade-${instance}`
|
|
577
|
-
const previewId = `prts-conversation-preview-${instance}`
|
|
578
|
-
const historyTooltipId = `prts-conversation-history-tooltip-${instance}`
|
|
579
|
-
const scale = document.createElement('div')
|
|
580
|
-
scale.setAttribute('data-prts-conversation-scale', '')
|
|
581
|
-
scale.setAttribute('data-prts-owned-conversation-scale', '')
|
|
582
|
-
scale.setAttribute('role', 'slider')
|
|
583
|
-
scale.setAttribute('aria-label', '会话刻度导航')
|
|
584
|
-
scale.setAttribute('aria-orientation', 'vertical')
|
|
585
|
-
scale.tabIndex = 0
|
|
586
|
-
|
|
587
|
-
const svg = document.createElementNS(SVG_NAMESPACE, 'svg')
|
|
588
|
-
svg.setAttribute('data-prts-conversation-scale-svg', '')
|
|
589
|
-
svg.setAttribute('aria-hidden', 'true')
|
|
590
|
-
svg.setAttribute('preserveAspectRatio', 'none')
|
|
591
|
-
const defs = document.createElementNS(SVG_NAMESPACE, 'defs')
|
|
592
|
-
const gradient = document.createElementNS(SVG_NAMESPACE, 'linearGradient')
|
|
593
|
-
gradient.id = `${maskId}-gradient`
|
|
594
|
-
gradient.setAttribute('gradientUnits', 'userSpaceOnUse')
|
|
595
|
-
gradient.setAttribute('x1', '0')
|
|
596
|
-
gradient.setAttribute('y1', '0')
|
|
597
|
-
gradient.setAttribute('x2', '0')
|
|
598
|
-
gradient.setAttribute('y2', '24')
|
|
599
|
-
const transparent = document.createElementNS(SVG_NAMESPACE, 'stop')
|
|
600
|
-
transparent.setAttribute('offset', '0')
|
|
601
|
-
transparent.setAttribute('stop-color', 'white')
|
|
602
|
-
transparent.setAttribute('stop-opacity', '0')
|
|
603
|
-
const opaque = document.createElementNS(SVG_NAMESPACE, 'stop')
|
|
604
|
-
opaque.setAttribute('offset', '1')
|
|
605
|
-
opaque.setAttribute('stop-color', 'white')
|
|
606
|
-
gradient.append(transparent, opaque)
|
|
607
|
-
const mask = document.createElementNS(SVG_NAMESPACE, 'mask')
|
|
608
|
-
mask.id = maskId
|
|
609
|
-
mask.setAttribute('maskUnits', 'userSpaceOnUse')
|
|
610
|
-
mask.setAttribute('x', '0')
|
|
611
|
-
mask.setAttribute('y', '0')
|
|
612
|
-
mask.setAttribute('width', String(SCALE_WIDTH))
|
|
613
|
-
const maskRect = document.createElementNS(SVG_NAMESPACE, 'rect')
|
|
614
|
-
maskRect.setAttribute('x', '0')
|
|
615
|
-
maskRect.setAttribute('y', '0')
|
|
616
|
-
maskRect.setAttribute('width', String(SCALE_WIDTH))
|
|
617
|
-
maskRect.setAttribute('fill', `url(#${gradient.id})`)
|
|
618
|
-
mask.append(maskRect)
|
|
619
|
-
defs.append(gradient, mask)
|
|
620
|
-
const marks = document.createElementNS(SVG_NAMESPACE, 'g')
|
|
621
|
-
marks.setAttribute('data-prts-conversation-scale-marks', '')
|
|
622
|
-
const gray = document.createElementNS(SVG_NAMESPACE, 'path')
|
|
623
|
-
gray.setAttribute('data-prts-conversation-scale-gray', '')
|
|
624
|
-
const yellow = document.createElementNS(SVG_NAMESPACE, 'path')
|
|
625
|
-
yellow.setAttribute('data-prts-conversation-scale-accent', '')
|
|
626
|
-
const history = document.createElementNS(SVG_NAMESPACE, 'path')
|
|
627
|
-
history.setAttribute('data-prts-conversation-scale-history', '')
|
|
628
|
-
marks.append(gray, yellow, history)
|
|
629
|
-
const hit = document.createElementNS(SVG_NAMESPACE, 'rect')
|
|
630
|
-
hit.setAttribute('data-prts-conversation-scale-hit', '')
|
|
631
|
-
hit.setAttribute('x', '0')
|
|
632
|
-
hit.setAttribute('y', '0')
|
|
633
|
-
hit.setAttribute('width', String(SCALE_WIDTH))
|
|
634
|
-
hit.setAttribute('fill', 'transparent')
|
|
635
|
-
svg.append(defs, marks, hit)
|
|
636
|
-
scale.append(svg)
|
|
637
|
-
scale._prtsSvg = svg
|
|
638
|
-
scale._prtsMarks = marks
|
|
639
|
-
scale._prtsMask = mask
|
|
640
|
-
scale._prtsMaskRect = maskRect
|
|
641
|
-
scale._prtsMaskId = maskId
|
|
642
|
-
scale._prtsGray = gray
|
|
643
|
-
scale._prtsAccent = yellow
|
|
644
|
-
scale._prtsHistory = history
|
|
645
|
-
scale._prtsHit = hit
|
|
646
|
-
scale._prtsPreview = createPreview(document, previewId)
|
|
647
|
-
const historyStatus = document.createElement('div')
|
|
648
|
-
historyStatus.setAttribute('data-prts-conversation-history-status', '')
|
|
649
|
-
historyStatus.setAttribute('role', 'status')
|
|
650
|
-
historyStatus.setAttribute('aria-live', 'polite')
|
|
651
|
-
historyStatus.hidden = true
|
|
652
|
-
const historyText = document.createElement('span')
|
|
653
|
-
historyText.setAttribute('data-prts-conversation-history-text', '')
|
|
654
|
-
const historyAction = document.createElement('button')
|
|
655
|
-
historyAction.type = 'button'
|
|
656
|
-
historyAction.setAttribute('data-prts-conversation-history-action', '')
|
|
657
|
-
historyStatus.append(historyText, historyAction)
|
|
658
|
-
const historyHint = document.createElement('div')
|
|
659
|
-
historyHint.setAttribute('data-prts-conversation-history-hint', '')
|
|
660
|
-
historyHint.setAttribute('data-prts-owned-conversation-scale', '')
|
|
661
|
-
historyHint.hidden = true
|
|
662
|
-
const historyTrigger = document.createElement('button')
|
|
663
|
-
historyTrigger.type = 'button'
|
|
664
|
-
historyTrigger.tabIndex = -1
|
|
665
|
-
historyTrigger.setAttribute('data-prts-conversation-history-trigger', '')
|
|
666
|
-
historyTrigger.setAttribute('aria-label', '载入更早内容并前往会话开头')
|
|
667
|
-
const historyTooltip = document.createElement('div')
|
|
668
|
-
historyTooltip.id = historyTooltipId
|
|
669
|
-
historyTooltip.setAttribute('data-prts-conversation-history-tooltip', '')
|
|
670
|
-
historyTooltip.setAttribute('role', 'tooltip')
|
|
671
|
-
historyTooltip.setAttribute('aria-hidden', 'true')
|
|
672
|
-
historyTooltip.hidden = true
|
|
673
|
-
const historyTooltipConnector = document.createElement('span')
|
|
674
|
-
historyTooltipConnector.setAttribute('data-prts-conversation-history-tooltip-connector', '')
|
|
675
|
-
historyTooltipConnector.setAttribute('aria-hidden', 'true')
|
|
676
|
-
const historyTooltipBody = document.createElement('div')
|
|
677
|
-
historyTooltipBody.setAttribute('data-prts-conversation-preview-body', '')
|
|
678
|
-
historyTooltipBody.setAttribute('data-prts-conversation-history-tooltip-body', '')
|
|
679
|
-
const historyTooltipHeader = document.createElement('div')
|
|
680
|
-
historyTooltipHeader.setAttribute('data-prts-conversation-preview-row', 'question')
|
|
681
|
-
const historyTooltipHeaderLabel = document.createElement('b')
|
|
682
|
-
historyTooltipHeaderLabel.textContent = 'H'
|
|
683
|
-
const historyTooltipHeaderText = document.createElement('span')
|
|
684
|
-
historyTooltipHeaderText.setAttribute('data-prts-conversation-history-tooltip-label', '')
|
|
685
|
-
historyTooltipHeaderText.textContent = '历史断点'
|
|
686
|
-
historyTooltipHeader.append(historyTooltipHeaderLabel, historyTooltipHeaderText)
|
|
687
|
-
const historyTooltipDetail = document.createElement('div')
|
|
688
|
-
historyTooltipDetail.setAttribute('data-prts-conversation-preview-row', 'answer')
|
|
689
|
-
const historyTooltipDetailLabel = document.createElement('b')
|
|
690
|
-
historyTooltipDetailLabel.textContent = '↥'
|
|
691
|
-
const historyTooltipContent = document.createElement('div')
|
|
692
|
-
historyTooltipContent.setAttribute('data-prts-conversation-history-tooltip-content', '')
|
|
693
|
-
const historyTooltipTitle = document.createElement('strong')
|
|
694
|
-
historyTooltipTitle.textContent = '更早内容尚未加载'
|
|
695
|
-
const historyTooltipDescription = document.createElement('span')
|
|
696
|
-
historyTooltipDescription.textContent = '点击断点,载入历史并前往会话开头'
|
|
697
|
-
historyTooltipContent.append(historyTooltipTitle, historyTooltipDescription)
|
|
698
|
-
historyTooltipDetail.append(historyTooltipDetailLabel, historyTooltipContent)
|
|
699
|
-
historyTooltipBody.append(historyTooltipHeader, historyTooltipDetail)
|
|
700
|
-
historyTooltip.append(historyTooltipConnector, historyTooltipBody)
|
|
701
|
-
historyHint.append(historyTrigger, historyTooltip)
|
|
702
|
-
scale._prtsHistoryStatus = historyStatus
|
|
703
|
-
scale._prtsHistoryText = historyText
|
|
704
|
-
scale._prtsHistoryAction = historyAction
|
|
705
|
-
scale._prtsHistoryHint = historyHint
|
|
706
|
-
scale._prtsHistoryTrigger = historyTrigger
|
|
707
|
-
scale._prtsHistoryTooltip = historyTooltip
|
|
708
|
-
return scale
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
export function createConversationScaleAdapter({ document, window, sessions }) {
|
|
712
|
-
let operation
|
|
713
|
-
let scroller
|
|
714
|
-
let composer
|
|
715
|
-
let scale
|
|
716
|
-
let preview
|
|
717
|
-
let historyStatus
|
|
718
|
-
let historyHint
|
|
719
|
-
let mutationObserver
|
|
720
|
-
let resizeObserver
|
|
721
|
-
let previewObserver
|
|
722
|
-
let previewSource
|
|
723
|
-
let frame
|
|
724
|
-
let waveFrame
|
|
725
|
-
let snapTimer
|
|
726
|
-
let touchTimer
|
|
727
|
-
let previewDelayTimer
|
|
728
|
-
let previewHideTimer
|
|
729
|
-
let previewRefreshTimer
|
|
730
|
-
let turns = []
|
|
731
|
-
let resizeSettleTimer
|
|
732
|
-
let resizeActive = false
|
|
733
|
-
let layout
|
|
734
|
-
let geometry
|
|
735
|
-
let scaleOffset = 0
|
|
736
|
-
let previousMaximum = 0
|
|
737
|
-
let followBottom = true
|
|
738
|
-
let cursorIndex = 0
|
|
739
|
-
let hoverIndex = -1
|
|
740
|
-
let previewIndex = -1
|
|
741
|
-
let pendingPreviewIndex = -1
|
|
742
|
-
let keyboardActive = false
|
|
743
|
-
let pointerInside = false
|
|
744
|
-
let pointerStart
|
|
745
|
-
let lastDirectJump = 0
|
|
746
|
-
let jumpRequest = 0
|
|
747
|
-
let historyRequest = 0
|
|
748
|
-
let historyMutationRevision = 0
|
|
749
|
-
let historyAvailable = false
|
|
750
|
-
let historyPhase = 'idle'
|
|
751
|
-
let historyPages = 0
|
|
752
|
-
let historyMessage = ''
|
|
753
|
-
let historySessionKey
|
|
754
|
-
let historyHintHovered = false
|
|
755
|
-
let scanPending = false
|
|
756
|
-
let fullScanPending = false
|
|
757
|
-
let pendingMutations = []
|
|
758
|
-
let obstructionDirty = true
|
|
759
|
-
const overlayCandidates = new Set()
|
|
760
|
-
let overlayScanPending = false
|
|
761
|
-
let scaleObstructed = false
|
|
762
|
-
let scaleBlockingObstructed = false
|
|
763
|
-
let scaleSpaceObstructed = true
|
|
764
|
-
let maximumDistance = CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE
|
|
765
|
-
let focusContrast = CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT
|
|
766
|
-
let scaleLengths = resolveConversationScaleLengths(focusContrast)
|
|
767
|
-
let contentLeft
|
|
768
|
-
let geometryDirty = true
|
|
769
|
-
let geometrySettleFrame
|
|
770
|
-
let geometrySettleFramesRemaining = 0
|
|
771
|
-
let geometrySettleStableFrames = 0
|
|
772
|
-
let geometrySettleSignature
|
|
773
|
-
let observedOperationWidth
|
|
774
|
-
let observedOperationHeight
|
|
775
|
-
let started = false
|
|
776
|
-
let failed = false
|
|
777
|
-
let waveCenter = 0
|
|
778
|
-
let waveIntensity = 0
|
|
779
|
-
let waveStartCenter = 0
|
|
780
|
-
let waveStartIntensity = 0
|
|
781
|
-
let waveTargetCenter = 0
|
|
782
|
-
let waveTargetIntensity = 0
|
|
783
|
-
let waveStartedAt = 0
|
|
784
|
-
|
|
785
|
-
const requestFrame = callback => window?.requestAnimationFrame?.(callback) ?? window?.setTimeout?.(callback, 0)
|
|
786
|
-
const cancelFrame = id => {
|
|
787
|
-
if (id === undefined) return
|
|
788
|
-
if (window?.cancelAnimationFrame) window.cancelAnimationFrame(id)
|
|
789
|
-
else window?.clearTimeout?.(id)
|
|
790
|
-
}
|
|
791
|
-
const currentTime = timestamp => Number.isFinite(timestamp)
|
|
792
|
-
? timestamp
|
|
793
|
-
: window?.performance?.now?.() ?? Date.now()
|
|
794
|
-
const phoneScaleHidden = () => Number(window?.innerWidth) <= 640
|
|
795
|
-
|
|
796
|
-
function currentGeometrySignature() {
|
|
797
|
-
if (!operation || !scroller) return undefined
|
|
798
|
-
const operationBox = operation.getBoundingClientRect?.()
|
|
799
|
-
const scrollerBox = scroller.getBoundingClientRect?.()
|
|
800
|
-
const composerBox = composer?.getBoundingClientRect?.()
|
|
801
|
-
if (!operationBox || !scrollerBox) return undefined
|
|
802
|
-
const values = [
|
|
803
|
-
operationBox.width,
|
|
804
|
-
operationBox.height,
|
|
805
|
-
scrollerBox.top - operationBox.top,
|
|
806
|
-
scrollerBox.bottom - operationBox.top,
|
|
807
|
-
scrollerBox.left - operationBox.left,
|
|
808
|
-
scrollerBox.right - operationBox.left,
|
|
809
|
-
composerBox ? composerBox.top - operationBox.top : -1,
|
|
810
|
-
composerBox ? composerBox.bottom - operationBox.top : -1,
|
|
811
|
-
composerBox ? composerBox.left - operationBox.left : -1,
|
|
812
|
-
composerBox ? composerBox.right - operationBox.left : -1,
|
|
813
|
-
]
|
|
814
|
-
if (!values.every(Number.isFinite)) return undefined
|
|
815
|
-
return values.map(value => Number(value.toFixed(2))).join(':')
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function sampleGeometryStabilization() {
|
|
819
|
-
geometrySettleFrame = undefined
|
|
820
|
-
if (!started || failed || geometrySettleFramesRemaining <= 0) return
|
|
821
|
-
geometrySettleFramesRemaining -= 1
|
|
822
|
-
const signature = currentGeometrySignature()
|
|
823
|
-
if (signature === undefined) {
|
|
824
|
-
geometrySettleStableFrames = 0
|
|
825
|
-
} else if (signature !== geometrySettleSignature) {
|
|
826
|
-
geometrySettleSignature = signature
|
|
827
|
-
geometrySettleStableFrames = 1
|
|
828
|
-
geometryDirty = true
|
|
829
|
-
obstructionDirty = true
|
|
830
|
-
schedule()
|
|
831
|
-
} else {
|
|
832
|
-
geometrySettleStableFrames += 1
|
|
833
|
-
}
|
|
834
|
-
if (geometrySettleStableFrames >= GEOMETRY_SETTLE_STABLE_FRAMES) {
|
|
835
|
-
geometrySettleFramesRemaining = 0
|
|
836
|
-
return
|
|
837
|
-
}
|
|
838
|
-
if (geometrySettleFramesRemaining > 0) {
|
|
839
|
-
geometrySettleFrame = requestFrame(sampleGeometryStabilization)
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
function requestGeometryStabilization() {
|
|
844
|
-
if (!started || failed || phoneScaleHidden()) return
|
|
845
|
-
geometrySettleFramesRemaining = GEOMETRY_SETTLE_FRAME_LIMIT
|
|
846
|
-
geometrySettleStableFrames = 0
|
|
847
|
-
if (geometrySettleFrame === undefined) {
|
|
848
|
-
geometrySettleFrame = requestFrame(sampleGeometryStabilization)
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function stopGeometryStabilization() {
|
|
853
|
-
cancelFrame(geometrySettleFrame)
|
|
854
|
-
geometrySettleFrame = undefined
|
|
855
|
-
geometrySettleFramesRemaining = 0
|
|
856
|
-
geometrySettleStableFrames = 0
|
|
857
|
-
geometrySettleSignature = undefined
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function onHostGeometryChange() {
|
|
861
|
-
if (phoneScaleHidden()) {
|
|
862
|
-
stopGeometryStabilization()
|
|
863
|
-
return
|
|
864
|
-
}
|
|
865
|
-
requestGeometryStabilization()
|
|
866
|
-
schedule(true)
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function reducedMotion() {
|
|
870
|
-
return window?.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
|
|
871
|
-
|| document.documentElement.dataset.prtsMotion === 'reduced'
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
function sampleWave(timestamp = currentTime()) {
|
|
875
|
-
if (waveFrame === undefined) return
|
|
876
|
-
const progress = clamp((currentTime(timestamp) - waveStartedAt) / CONVERSATION_SCALE_WAVE_DURATION, 0, 1)
|
|
877
|
-
const eased = 1 - (1 - progress) ** 3
|
|
878
|
-
waveCenter = waveStartCenter + (waveTargetCenter - waveStartCenter) * eased
|
|
879
|
-
waveIntensity = waveStartIntensity + (waveTargetIntensity - waveStartIntensity) * eased
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
function animateWave(timestamp) {
|
|
883
|
-
waveFrame = undefined
|
|
884
|
-
if (!started || failed) return
|
|
885
|
-
const time = currentTime(timestamp)
|
|
886
|
-
const progress = clamp((time - waveStartedAt) / CONVERSATION_SCALE_WAVE_DURATION, 0, 1)
|
|
887
|
-
const eased = 1 - (1 - progress) ** 3
|
|
888
|
-
waveCenter = waveStartCenter + (waveTargetCenter - waveStartCenter) * eased
|
|
889
|
-
waveIntensity = waveStartIntensity + (waveTargetIntensity - waveStartIntensity) * eased
|
|
890
|
-
schedule()
|
|
891
|
-
if (progress < 1) waveFrame = requestFrame(animateWave)
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function retargetWave(index) {
|
|
895
|
-
const time = currentTime()
|
|
896
|
-
sampleWave(time)
|
|
897
|
-
cancelFrame(waveFrame)
|
|
898
|
-
waveFrame = undefined
|
|
899
|
-
waveStartCenter = waveCenter
|
|
900
|
-
waveStartIntensity = waveIntensity
|
|
901
|
-
waveTargetCenter = index >= 0 ? index : waveCenter
|
|
902
|
-
waveTargetIntensity = index >= 0 ? 1 : 0
|
|
903
|
-
waveStartedAt = time
|
|
904
|
-
if (reducedMotion()) {
|
|
905
|
-
waveCenter = waveTargetCenter
|
|
906
|
-
waveIntensity = waveTargetIntensity
|
|
907
|
-
schedule()
|
|
908
|
-
return
|
|
909
|
-
}
|
|
910
|
-
waveFrame = requestFrame(animateWave)
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
function resetWave() {
|
|
914
|
-
cancelFrame(waveFrame)
|
|
915
|
-
waveFrame = undefined
|
|
916
|
-
waveCenter = 0
|
|
917
|
-
waveIntensity = 0
|
|
918
|
-
waveStartCenter = 0
|
|
919
|
-
waveStartIntensity = 0
|
|
920
|
-
waveTargetCenter = 0
|
|
921
|
-
waveTargetIntensity = 0
|
|
922
|
-
waveStartedAt = 0
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
function cancelJump() {
|
|
926
|
-
jumpRequest += 1
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
function historyPrefix() {
|
|
930
|
-
return historyAvailable ? 1 : 0
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function scaleItemCount() {
|
|
934
|
-
return turns.length + historyPrefix()
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function scaleToTurnIndex(index) {
|
|
938
|
-
return index - historyPrefix()
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function turnToScaleIndex(index) {
|
|
942
|
-
return index + historyPrefix()
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function syncHistoryStatus() {
|
|
946
|
-
if (!historyStatus || !scale) return
|
|
947
|
-
const visible = historyPhase === 'loading' || historyPhase === 'paused' || historyPhase === 'error'
|
|
948
|
-
historyStatus.hidden = !visible
|
|
949
|
-
historyStatus.dataset.prtsHistoryPhase = historyPhase
|
|
950
|
-
scale.dataset.prtsHistoryPhase = historyPhase
|
|
951
|
-
scale.toggleAttribute('data-prts-conversation-history-available', historyAvailable)
|
|
952
|
-
if (!visible) {
|
|
953
|
-
syncHistoryHint()
|
|
954
|
-
return
|
|
955
|
-
}
|
|
956
|
-
const page = historyPhase === 'loading' ? historyPages + 1 : historyPages
|
|
957
|
-
const fallback = historyPhase === 'loading'
|
|
958
|
-
? `正在载入更早内容 · 第 ${page} 页`
|
|
959
|
-
: historyPhase === 'error'
|
|
960
|
-
? historyMessage || '更早内容载入失败'
|
|
961
|
-
: historyPages > 0
|
|
962
|
-
? `已载入 ${historyPages} 页 · 尚未到达开头`
|
|
963
|
-
: '已载入部分内容 · 尚未到达开头'
|
|
964
|
-
scale._prtsHistoryText.textContent = fallback
|
|
965
|
-
scale._prtsHistoryAction.textContent = historyPhase === 'loading' ? '取消' : '继续'
|
|
966
|
-
scale._prtsHistoryAction.setAttribute(
|
|
967
|
-
'aria-label',
|
|
968
|
-
historyPhase === 'loading' ? '取消前往会话开头' : '继续前往会话开头',
|
|
969
|
-
)
|
|
970
|
-
syncHistoryHint()
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
function syncHistoryHint() {
|
|
974
|
-
if (!historyHint || !scale) return
|
|
975
|
-
const railUnavailable = scaleSpaceObstructed || scaleBlockingObstructed || scaleObstructed
|
|
976
|
-
|| scaleItemCount() < MINIMUM_TURNS
|
|
977
|
-
const visible = historyAvailable && historyPhase === 'idle' && !railUnavailable
|
|
978
|
-
if (!visible) historyHintHovered = false
|
|
979
|
-
const selected = visible && (
|
|
980
|
-
historyHintHovered
|
|
981
|
-
|| (pointerInside && hoverIndex === 0)
|
|
982
|
-
)
|
|
983
|
-
historyHint.hidden = !visible
|
|
984
|
-
historyHint.toggleAttribute('data-prts-history-selected', selected)
|
|
985
|
-
scale.toggleAttribute('data-prts-conversation-history-selected', selected)
|
|
986
|
-
const tooltip = scale._prtsHistoryTooltip
|
|
987
|
-
tooltip.hidden = !selected
|
|
988
|
-
tooltip.setAttribute('aria-hidden', String(!selected))
|
|
989
|
-
const tooltipId = tooltip.id
|
|
990
|
-
for (const target of [scale, scale._prtsHistoryTrigger]) {
|
|
991
|
-
if (selected) target.setAttribute('aria-describedby', tooltipId)
|
|
992
|
-
else if (target.getAttribute('aria-describedby') === tooltipId) target.removeAttribute('aria-describedby')
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function selectHistoryHint({ keyboard = false } = {}) {
|
|
997
|
-
if (!historyAvailable || historyPhase !== 'idle') return
|
|
998
|
-
keyboardActive = keyboard
|
|
999
|
-
hoverIndex = 0
|
|
1000
|
-
cursorIndex = 0
|
|
1001
|
-
ensureVisible(0)
|
|
1002
|
-
retargetWave(0)
|
|
1003
|
-
syncAccessibility()
|
|
1004
|
-
schedule()
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
function setHistoryAvailable(next) {
|
|
1008
|
-
const available = Boolean(next)
|
|
1009
|
-
if (available === historyAvailable) return
|
|
1010
|
-
const previousPrefix = historyPrefix()
|
|
1011
|
-
historyAvailable = available
|
|
1012
|
-
const delta = historyPrefix() - previousPrefix
|
|
1013
|
-
if (cursorIndex >= previousPrefix) cursorIndex = Math.max(0, cursorIndex + delta)
|
|
1014
|
-
if (hoverIndex >= previousPrefix) hoverIndex = Math.max(0, hoverIndex + delta)
|
|
1015
|
-
if (!followBottom && scaleOffset > 0) scaleOffset = Math.max(0, scaleOffset + delta * CONVERSATION_SCALE_SPACING)
|
|
1016
|
-
geometryDirty = true
|
|
1017
|
-
syncHistoryStatus()
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
function readHistoryState() {
|
|
1021
|
-
const snapshot = resolveConversationHistorySnapshot(sessions)
|
|
1022
|
-
const olderButton = findConversationOlderButton(operation)
|
|
1023
|
-
if (snapshot.key !== undefined && snapshot.key !== historySessionKey) {
|
|
1024
|
-
historyRequest += 1
|
|
1025
|
-
historySessionKey = snapshot.key
|
|
1026
|
-
historyPages = 0
|
|
1027
|
-
historyPhase = 'idle'
|
|
1028
|
-
historyMessage = ''
|
|
1029
|
-
}
|
|
1030
|
-
const available = snapshot.hasMore || Boolean(olderButton) || historyPhase === 'loading'
|
|
1031
|
-
setHistoryAvailable(available)
|
|
1032
|
-
if (!available && historyPhase !== 'loading') {
|
|
1033
|
-
historyPhase = 'idle'
|
|
1034
|
-
historyMessage = ''
|
|
1035
|
-
syncHistoryStatus()
|
|
1036
|
-
}
|
|
1037
|
-
return { ...snapshot, olderButton }
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
function cancelHistory({ paused = true } = {}) {
|
|
1041
|
-
if (historyPhase !== 'loading') return
|
|
1042
|
-
historyRequest += 1
|
|
1043
|
-
historyPhase = paused && historyAvailable ? 'paused' : 'idle'
|
|
1044
|
-
historyMessage = ''
|
|
1045
|
-
syncHistoryStatus()
|
|
1046
|
-
schedule()
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
function cancelNavigation() {
|
|
1050
|
-
cancelJump()
|
|
1051
|
-
cancelHistory()
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
function onHistoryAction(event) {
|
|
1055
|
-
event.preventDefault()
|
|
1056
|
-
event.stopPropagation()
|
|
1057
|
-
if (historyPhase === 'loading') cancelHistory()
|
|
1058
|
-
else void loadHistoryToStart()
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
function onHistoryHintPointerEnter() {
|
|
1062
|
-
historyHintHovered = true
|
|
1063
|
-
selectHistoryHint()
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
function onHistoryHintPointerLeave() {
|
|
1067
|
-
historyHintHovered = false
|
|
1068
|
-
if (!pointerInside) {
|
|
1069
|
-
hoverIndex = -1
|
|
1070
|
-
retargetWave(-1)
|
|
1071
|
-
}
|
|
1072
|
-
schedule()
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
function schedule(scan = false) {
|
|
1076
|
-
if (scan) {
|
|
1077
|
-
scanPending = true
|
|
1078
|
-
fullScanPending = true
|
|
1079
|
-
}
|
|
1080
|
-
if (frame !== undefined || failed) return
|
|
1081
|
-
frame = requestFrame(flush)
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
function clearTimers() {
|
|
1085
|
-
for (const timer of [snapTimer, touchTimer, previewDelayTimer, previewHideTimer, previewRefreshTimer, resizeSettleTimer]) {
|
|
1086
|
-
if (timer !== undefined) window?.clearTimeout?.(timer)
|
|
1087
|
-
}
|
|
1088
|
-
snapTimer = undefined
|
|
1089
|
-
touchTimer = undefined
|
|
1090
|
-
previewDelayTimer = undefined
|
|
1091
|
-
previewHideTimer = undefined
|
|
1092
|
-
previewRefreshTimer = undefined
|
|
1093
|
-
resizeSettleTimer = undefined
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
function setScaleResizeState(active) {
|
|
1097
|
-
resizeActive = Boolean(active)
|
|
1098
|
-
for (const node of [scale, preview, historyHint, historyStatus, composer]) {
|
|
1099
|
-
if (!node || node.hasAttribute('data-prts-resizing') === resizeActive) continue
|
|
1100
|
-
node.toggleAttribute('data-prts-resizing', resizeActive)
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
function disconnectPreviewObserver() {
|
|
1105
|
-
resizeSettleTimer = undefined
|
|
1106
|
-
previewObserver?.disconnect()
|
|
1107
|
-
previewObserver = undefined
|
|
1108
|
-
previewSource = undefined
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
function observePreviewSource(source) {
|
|
1112
|
-
if (source === previewSource) return
|
|
1113
|
-
disconnectPreviewObserver()
|
|
1114
|
-
const MutationObserver = window?.MutationObserver
|
|
1115
|
-
if (!source || typeof MutationObserver !== 'function') return
|
|
1116
|
-
previewSource = source
|
|
1117
|
-
previewObserver = new MutationObserver(queuePreviewRefresh)
|
|
1118
|
-
previewObserver.observe(source, {
|
|
1119
|
-
subtree: true,
|
|
1120
|
-
childList: true,
|
|
1121
|
-
characterData: true,
|
|
1122
|
-
attributes: true,
|
|
1123
|
-
attributeFilter: ['hidden', 'aria-hidden'],
|
|
1124
|
-
})
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
function resolveRoots() {
|
|
1128
|
-
const nextOperation = document?.querySelector?.('[data-prts-region="operation"]')
|
|
1129
|
-
const nextScroller = nextOperation?.querySelector?.('[data-conversation-scroll]')
|
|
1130
|
-
if (!nextOperation || !nextScroller) {
|
|
1131
|
-
if (operation || scroller || scale || preview) disconnectRoots()
|
|
1132
|
-
return false
|
|
1133
|
-
}
|
|
1134
|
-
const nextComposer = nextScroller.querySelector('[data-composer-seat], [data-slot="conversation.composer"]')
|
|
1135
|
-
if (operation === nextOperation && scroller === nextScroller && scale?.isConnected && preview?.isConnected
|
|
1136
|
-
&& historyHint?.isConnected && historyStatus?.isConnected) {
|
|
1137
|
-
if (composer !== nextComposer) {
|
|
1138
|
-
if (composer) resizeObserver?.unobserve?.(composer)
|
|
1139
|
-
composer = nextComposer
|
|
1140
|
-
if (composer) resizeObserver?.observe(composer)
|
|
1141
|
-
geometryDirty = true
|
|
1142
|
-
}
|
|
1143
|
-
return true
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
disconnectRoots()
|
|
1147
|
-
operation = nextOperation
|
|
1148
|
-
scroller = nextScroller
|
|
1149
|
-
composer = nextComposer
|
|
1150
|
-
scale = createScale(document)
|
|
1151
|
-
preview = scale._prtsPreview
|
|
1152
|
-
historyHint = scale._prtsHistoryHint
|
|
1153
|
-
historyStatus = scale._prtsHistoryStatus
|
|
1154
|
-
operation.append(scale, preview, historyHint, historyStatus)
|
|
1155
|
-
scale._prtsHistoryTrigger.addEventListener('click', onHistoryAction)
|
|
1156
|
-
scale._prtsHistoryTrigger.addEventListener('pointerenter', onHistoryHintPointerEnter)
|
|
1157
|
-
scale._prtsHistoryTrigger.addEventListener('pointerleave', onHistoryHintPointerLeave)
|
|
1158
|
-
scale._prtsHistoryAction.addEventListener('click', onHistoryAction)
|
|
1159
|
-
scroller.setAttribute('data-prts-conversation-scale-ready', '')
|
|
1160
|
-
scale.addEventListener('pointermove', onPointerMove)
|
|
1161
|
-
scale.addEventListener('pointerleave', onPointerLeave)
|
|
1162
|
-
scale.addEventListener('pointerdown', onPointerDown)
|
|
1163
|
-
scale.addEventListener('pointerup', onPointerUp)
|
|
1164
|
-
scale.addEventListener('pointercancel', onPointerCancel)
|
|
1165
|
-
scale.addEventListener('click', onClick)
|
|
1166
|
-
scale.addEventListener('wheel', onWheel, { passive: false })
|
|
1167
|
-
scale.addEventListener('keydown', onKeydown)
|
|
1168
|
-
scale.addEventListener('focus', onFocus)
|
|
1169
|
-
scale.addEventListener('blur', onBlur)
|
|
1170
|
-
scroller.addEventListener('wheel', cancelNavigation, { passive: true })
|
|
1171
|
-
scroller.addEventListener('touchstart', cancelNavigation, { passive: true })
|
|
1172
|
-
scroller.addEventListener('pointerdown', cancelNavigation, { passive: true })
|
|
1173
|
-
resizeObserver?.observe(operation)
|
|
1174
|
-
resizeObserver?.observe(scroller)
|
|
1175
|
-
if (composer) resizeObserver?.observe(composer)
|
|
1176
|
-
turns = []
|
|
1177
|
-
scaleOffset = 0
|
|
1178
|
-
previousMaximum = 0
|
|
1179
|
-
followBottom = true
|
|
1180
|
-
obstructionDirty = true
|
|
1181
|
-
cursorIndex = 0
|
|
1182
|
-
hoverIndex = -1
|
|
1183
|
-
previewIndex = -1
|
|
1184
|
-
pendingPreviewIndex = -1
|
|
1185
|
-
contentLeft = undefined
|
|
1186
|
-
geometryDirty = true
|
|
1187
|
-
scaleSpaceObstructed = true
|
|
1188
|
-
overlayScanPending = true
|
|
1189
|
-
historyRequest += 1
|
|
1190
|
-
historyAvailable = false
|
|
1191
|
-
historyPhase = 'idle'
|
|
1192
|
-
historyPages = 0
|
|
1193
|
-
historyMessage = ''
|
|
1194
|
-
historySessionKey = undefined
|
|
1195
|
-
historyHintHovered = false
|
|
1196
|
-
setScaleResizeState(resizeActive)
|
|
1197
|
-
cancelJump()
|
|
1198
|
-
resetWave()
|
|
1199
|
-
requestGeometryStabilization()
|
|
1200
|
-
return true
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
function disconnectRoots() {
|
|
1204
|
-
stopGeometryStabilization()
|
|
1205
|
-
clearTimers()
|
|
1206
|
-
hidePreview()
|
|
1207
|
-
disconnectPreviewObserver()
|
|
1208
|
-
if (scroller) {
|
|
1209
|
-
scroller.removeAttribute('data-prts-conversation-scale-ready')
|
|
1210
|
-
scroller.removeAttribute('data-prts-conversation-scale-empty')
|
|
1211
|
-
scroller.removeEventListener('wheel', cancelNavigation)
|
|
1212
|
-
scroller.removeEventListener('touchstart', cancelNavigation)
|
|
1213
|
-
scroller.removeEventListener('pointerdown', cancelNavigation)
|
|
1214
|
-
}
|
|
1215
|
-
if (scale) {
|
|
1216
|
-
scale.removeEventListener('pointermove', onPointerMove)
|
|
1217
|
-
scale.removeEventListener('pointerleave', onPointerLeave)
|
|
1218
|
-
scale.removeEventListener('pointerdown', onPointerDown)
|
|
1219
|
-
scale.removeEventListener('pointerup', onPointerUp)
|
|
1220
|
-
scale.removeEventListener('pointercancel', onPointerCancel)
|
|
1221
|
-
scale.removeEventListener('click', onClick)
|
|
1222
|
-
scale.removeEventListener('wheel', onWheel)
|
|
1223
|
-
scale.removeEventListener('keydown', onKeydown)
|
|
1224
|
-
scale.removeEventListener('focus', onFocus)
|
|
1225
|
-
scale.removeEventListener('blur', onBlur)
|
|
1226
|
-
scale._prtsHistoryTrigger?.removeEventListener('click', onHistoryAction)
|
|
1227
|
-
scale._prtsHistoryTrigger?.removeEventListener('pointerenter', onHistoryHintPointerEnter)
|
|
1228
|
-
scale._prtsHistoryTrigger?.removeEventListener('pointerleave', onHistoryHintPointerLeave)
|
|
1229
|
-
scale._prtsHistoryAction?.removeEventListener('click', onHistoryAction)
|
|
1230
|
-
scale.remove()
|
|
1231
|
-
}
|
|
1232
|
-
preview?.remove()
|
|
1233
|
-
historyHint?.remove()
|
|
1234
|
-
historyStatus?.remove()
|
|
1235
|
-
resizeObserver?.disconnect()
|
|
1236
|
-
historyRequest += 1
|
|
1237
|
-
operation = undefined
|
|
1238
|
-
scroller = undefined
|
|
1239
|
-
composer = undefined
|
|
1240
|
-
scale = undefined
|
|
1241
|
-
preview = undefined
|
|
1242
|
-
historyHint = undefined
|
|
1243
|
-
historyStatus = undefined
|
|
1244
|
-
turns = []
|
|
1245
|
-
layout = undefined
|
|
1246
|
-
geometry = undefined
|
|
1247
|
-
hoverIndex = -1
|
|
1248
|
-
previewIndex = -1
|
|
1249
|
-
keyboardActive = false
|
|
1250
|
-
pointerInside = false
|
|
1251
|
-
pointerStart = undefined
|
|
1252
|
-
historyHintHovered = false
|
|
1253
|
-
resizeActive = false
|
|
1254
|
-
cancelJump()
|
|
1255
|
-
resetWave()
|
|
1256
|
-
overlayCandidates.clear()
|
|
1257
|
-
obstructionDirty = true
|
|
1258
|
-
overlayScanPending = false
|
|
1259
|
-
scaleObstructed = false
|
|
1260
|
-
scaleBlockingObstructed = false
|
|
1261
|
-
scaleSpaceObstructed = true
|
|
1262
|
-
contentLeft = undefined
|
|
1263
|
-
observedOperationWidth = undefined
|
|
1264
|
-
observedOperationHeight = undefined
|
|
1265
|
-
geometryDirty = true
|
|
1266
|
-
pendingMutations = []
|
|
1267
|
-
fullScanPending = false
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
function isTopLevelTurn(node) {
|
|
1271
|
-
if (!node?.matches?.(CONVERSATION_USER_TURN_SELECTOR) || !scroller?.contains(node) || !visible(node)) return false
|
|
1272
|
-
if (node.closest('[data-prts-conversation-scale], [data-prts-conversation-preview]')) return false
|
|
1273
|
-
const parent = node.parentElement?.closest?.(CONVERSATION_USER_TURN_SELECTOR)
|
|
1274
|
-
return !parent || !scroller.contains(parent)
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
function collectTurnsFromNode(node, target) {
|
|
1278
|
-
if (node?.nodeType !== 1) return
|
|
1279
|
-
if (node.matches?.(CONVERSATION_USER_TURN_SELECTOR)) target.add(node)
|
|
1280
|
-
for (const turn of node.querySelectorAll?.(CONVERSATION_USER_TURN_SELECTOR) ?? []) target.add(turn)
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
function collectOverlayCandidates(node) {
|
|
1284
|
-
if (node?.nodeType !== 1) return
|
|
1285
|
-
if (node.matches?.(CONVERSATION_OVERLAY_SELECTOR)) overlayCandidates.add(node)
|
|
1286
|
-
for (const candidate of node.querySelectorAll?.(CONVERSATION_OVERLAY_SELECTOR) ?? []) {
|
|
1287
|
-
overlayCandidates.add(candidate)
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
function mutationNodeAffectsScale(node) {
|
|
1292
|
-
if (node?.nodeType !== 1) return false
|
|
1293
|
-
if (node.matches?.(CONVERSATION_OVERLAY_SELECTOR)
|
|
1294
|
-
|| node.matches?.(CONVERSATION_PORTAL_ROOT_SELECTOR)
|
|
1295
|
-
|| node.matches?.('[data-prts-region="operation"], [data-slot="conversation"]')) return true
|
|
1296
|
-
return Boolean(node.querySelector?.([
|
|
1297
|
-
CONVERSATION_OVERLAY_SELECTOR,
|
|
1298
|
-
CONVERSATION_PORTAL_ROOT_SELECTOR,
|
|
1299
|
-
'[data-prts-region="operation"]',
|
|
1300
|
-
'[data-slot="conversation"]',
|
|
1301
|
-
].join(', ')))
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
function mutationAffectsTurns(mutation) {
|
|
1305
|
-
if (mutation.type === 'attributes') {
|
|
1306
|
-
if (mutation.attributeName === 'data-chat-flow-kind'
|
|
1307
|
-
|| mutation.attributeName === 'data-message-role') return true
|
|
1308
|
-
if (mutation.target?.matches?.(CONVERSATION_USER_TURN_SELECTOR)
|
|
1309
|
-
|| mutation.target?.querySelector?.(CONVERSATION_USER_TURN_SELECTOR)) return true
|
|
1310
|
-
}
|
|
1311
|
-
for (const node of [...(mutation.addedNodes ?? []), ...(mutation.removedNodes ?? [])]) {
|
|
1312
|
-
if (node?.nodeType !== 1) continue
|
|
1313
|
-
if (node.matches?.(CONVERSATION_USER_TURN_SELECTOR)
|
|
1314
|
-
|| node.querySelector?.(CONVERSATION_USER_TURN_SELECTOR)) return true
|
|
1315
|
-
}
|
|
1316
|
-
return false
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
function mutationAffectsOverlay(mutation) {
|
|
1320
|
-
if (overlayCandidates.has(mutation.target)) return true
|
|
1321
|
-
if (mutationNodeAffectsScale(mutation.target)) return true
|
|
1322
|
-
for (const node of mutation.addedNodes ?? []) if (mutationNodeAffectsScale(node)) return true
|
|
1323
|
-
for (const node of mutation.removedNodes ?? []) {
|
|
1324
|
-
if (mutationNodeAffectsScale(node)) return true
|
|
1325
|
-
if ([...overlayCandidates].some(candidate => candidate === node || node?.contains?.(candidate))) return true
|
|
1326
|
-
}
|
|
1327
|
-
return false
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
function collectExternalOverlayCandidates() {
|
|
1331
|
-
for (const child of document?.body?.children ?? []) {
|
|
1332
|
-
if (child === operation || child.contains?.(operation)) continue
|
|
1333
|
-
collectOverlayCandidates(child)
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
function updateOverlayCandidates(mutations, full = false) {
|
|
1338
|
-
if (full || overlayScanPending) {
|
|
1339
|
-
overlayCandidates.clear()
|
|
1340
|
-
collectOverlayCandidates(operation)
|
|
1341
|
-
collectExternalOverlayCandidates()
|
|
1342
|
-
overlayScanPending = false
|
|
1343
|
-
} else {
|
|
1344
|
-
for (const mutation of mutations) {
|
|
1345
|
-
if (mutation.type === 'attributes') {
|
|
1346
|
-
if (mutation.target?.matches?.(CONVERSATION_OVERLAY_SELECTOR)) overlayCandidates.add(mutation.target)
|
|
1347
|
-
else overlayCandidates.delete(mutation.target)
|
|
1348
|
-
}
|
|
1349
|
-
for (const node of mutation.addedNodes ?? []) collectOverlayCandidates(node)
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
for (const candidate of [...overlayCandidates]) {
|
|
1353
|
-
if (!candidate.isConnected) overlayCandidates.delete(candidate)
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
function applyTurns(nextTurns) {
|
|
1358
|
-
const wasAtBottom = shouldFollowConversationScaleBottom(scaleOffset, previousMaximum)
|
|
1359
|
-
const previousList = turns
|
|
1360
|
-
const previousTurns = new Set(previousList)
|
|
1361
|
-
const previousCount = turns.length
|
|
1362
|
-
const previousPrefix = historyPrefix()
|
|
1363
|
-
const turnAtScaleIndex = index => previousList[index - previousPrefix]
|
|
1364
|
-
const cursorTurn = turnAtScaleIndex(cursorIndex)
|
|
1365
|
-
const hoverTurn = turnAtScaleIndex(hoverIndex)
|
|
1366
|
-
const cursorWasHistory = historyAvailable && cursorIndex === 0
|
|
1367
|
-
const hoverWasHistory = historyAvailable && hoverIndex === 0
|
|
1368
|
-
const previewTurn = previousList[previewIndex]
|
|
1369
|
-
const pendingPreviewTurn = previousList[pendingPreviewIndex]
|
|
1370
|
-
const previewWasVisible = previewIndex >= 0
|
|
1371
|
-
const anchorScaleIndex = layout
|
|
1372
|
-
? nearestVisibleConversationTick(layout, .5)
|
|
1373
|
-
: -1
|
|
1374
|
-
const anchorTurn = turnAtScaleIndex(anchorScaleIndex)
|
|
1375
|
-
const anchorY = layout && anchorScaleIndex >= 0
|
|
1376
|
-
? layout.start + anchorScaleIndex * CONVERSATION_SCALE_SPACING - scaleOffset
|
|
1377
|
-
: undefined
|
|
1378
|
-
const replaced = previousCount > 0 && nextTurns.length > 0 && !nextTurns.some(turn => previousTurns.has(turn))
|
|
1379
|
-
turns = nextTurns
|
|
1380
|
-
if (replaced || turns.length !== previousCount) geometryDirty = true
|
|
1381
|
-
if (replaced || turns.length > previousCount) cancelJump()
|
|
1382
|
-
followBottom = replaced || previousCount === 0 || (turns.length > previousCount && wasAtBottom)
|
|
1383
|
-
const nextScaleIndex = node => {
|
|
1384
|
-
const index = node ? turns.indexOf(node) : -1
|
|
1385
|
-
return index < 0 ? -1 : turnToScaleIndex(index)
|
|
1386
|
-
}
|
|
1387
|
-
cursorIndex = cursorWasHistory ? 0 : nextScaleIndex(cursorTurn)
|
|
1388
|
-
if (cursorIndex < 0) cursorIndex = Math.max(0, scaleItemCount() - 1)
|
|
1389
|
-
hoverIndex = hoverWasHistory ? 0 : nextScaleIndex(hoverTurn)
|
|
1390
|
-
previewIndex = previewTurn ? turns.indexOf(previewTurn) : -1
|
|
1391
|
-
pendingPreviewIndex = pendingPreviewTurn ? turns.indexOf(pendingPreviewTurn) : -1
|
|
1392
|
-
if (previewWasVisible && previewIndex < 0) hidePreview()
|
|
1393
|
-
if (!followBottom && anchorTurn && Number.isFinite(anchorY)) {
|
|
1394
|
-
const nextAnchor = nextScaleIndex(anchorTurn)
|
|
1395
|
-
if (nextAnchor >= 0) {
|
|
1396
|
-
scaleOffset = Math.max(0, nextAnchor * CONVERSATION_SCALE_SPACING + (layout?.start ?? .5) - anchorY)
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
if ((previousCount === 0 || replaced) && turns.length) cursorIndex = scaleItemCount() - 1
|
|
1400
|
-
const empty = scaleItemCount() < MINIMUM_TURNS
|
|
1401
|
-
scale.dataset.prtsScaleCount = String(turns.length)
|
|
1402
|
-
scale.toggleAttribute('data-prts-conversation-scale-partial', historyAvailable)
|
|
1403
|
-
scale.toggleAttribute('data-prts-conversation-scale-empty', empty)
|
|
1404
|
-
scroller.toggleAttribute('data-prts-conversation-scale-empty', empty)
|
|
1405
|
-
syncAccessibility()
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
function syncTurns(mutations = [], full = false) {
|
|
1409
|
-
updateOverlayCandidates(mutations, full)
|
|
1410
|
-
const conversationMutations = mutations.filter(mutation => operation?.contains?.(mutation.target))
|
|
1411
|
-
if (full || !turns.length) {
|
|
1412
|
-
applyTurns(collectConversationTurns(scroller))
|
|
1413
|
-
return
|
|
1414
|
-
}
|
|
1415
|
-
const next = new Set(turns.filter(turn => turn.isConnected && scroller.contains(turn)))
|
|
1416
|
-
const touched = new Set()
|
|
1417
|
-
for (const mutation of conversationMutations) {
|
|
1418
|
-
if (mutation.type === 'attributes') {
|
|
1419
|
-
collectTurnsFromNode(mutation.target, touched)
|
|
1420
|
-
const closest = mutation.target?.closest?.(CONVERSATION_USER_TURN_SELECTOR)
|
|
1421
|
-
if (closest) touched.add(closest)
|
|
1422
|
-
}
|
|
1423
|
-
for (const node of mutation.addedNodes ?? []) collectTurnsFromNode(node, touched)
|
|
1424
|
-
for (const node of mutation.removedNodes ?? []) {
|
|
1425
|
-
if (node?.nodeType !== 1) continue
|
|
1426
|
-
for (const turn of next) {
|
|
1427
|
-
if (turn === node || node.contains?.(turn)) next.delete(turn)
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
for (const turn of touched) {
|
|
1432
|
-
if (isTopLevelTurn(turn)) next.add(turn)
|
|
1433
|
-
else next.delete(turn)
|
|
1434
|
-
}
|
|
1435
|
-
const ordered = [...next].filter(isTopLevelTurn).sort((left, right) => {
|
|
1436
|
-
if (left === right) return 0
|
|
1437
|
-
return follows(left, right) ? -1 : 1
|
|
1438
|
-
})
|
|
1439
|
-
applyTurns(ordered)
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
function representativeContentLeft(operationBox) {
|
|
1443
|
-
if (!geometryDirty) return contentLeft
|
|
1444
|
-
const assistant = scroller.querySelector?.([
|
|
1445
|
-
'[data-prts-ai-surface]:not([hidden])',
|
|
1446
|
-
'[data-chat-flow-kind="assistant-step"]:not([hidden])',
|
|
1447
|
-
'[data-message-role="assistant"]:not([hidden])',
|
|
1448
|
-
].join(', '))
|
|
1449
|
-
const representatives = [assistant, turns[0], turns.at(-1)].filter(Boolean)
|
|
1450
|
-
const positions = []
|
|
1451
|
-
for (const node of representatives) {
|
|
1452
|
-
if (!visible(node)) continue
|
|
1453
|
-
const style = window?.getComputedStyle?.(node)
|
|
1454
|
-
if (style?.display === 'none' || style?.visibility === 'hidden') continue
|
|
1455
|
-
const box = node.getBoundingClientRect?.()
|
|
1456
|
-
if (!box || box.width <= 0 || box.height <= 0) continue
|
|
1457
|
-
positions.push(box.left - operationBox.left)
|
|
1458
|
-
}
|
|
1459
|
-
contentLeft = positions.length ? Math.min(...positions) : undefined
|
|
1460
|
-
geometryDirty = false
|
|
1461
|
-
return contentLeft
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
function visibleGeometry() {
|
|
1465
|
-
const operationBox = operation.getBoundingClientRect()
|
|
1466
|
-
const scrollerBox = scroller.getBoundingClientRect()
|
|
1467
|
-
const composerBox = composer?.getBoundingClientRect?.()
|
|
1468
|
-
const top = clamp(scrollerBox.top - operationBox.top + 12, 8, Math.max(8, operationBox.height - 92))
|
|
1469
|
-
const bottomEdge = composerBox && composerBox.height > 0 && composerBox.top < operationBox.bottom
|
|
1470
|
-
? composerBox.top - operationBox.top - 12
|
|
1471
|
-
: Math.min(scrollerBox.bottom, operationBox.bottom) - operationBox.top - 18
|
|
1472
|
-
const height = Math.max(84, bottomEdge - top)
|
|
1473
|
-
const drawerMode = document.documentElement.dataset.prtsDrawerMode
|
|
1474
|
-
const scaleGeometry = drawerMode === 'overlay'
|
|
1475
|
-
? { visible: false, reason: 'drawer' }
|
|
1476
|
-
: window.innerWidth <= 640
|
|
1477
|
-
? { visible: false, reason: 'phone' }
|
|
1478
|
-
: resolveConversationScaleGeometry({
|
|
1479
|
-
operationWidth: operationBox.width,
|
|
1480
|
-
contentLeft: representativeContentLeft(operationBox),
|
|
1481
|
-
maximumDistance,
|
|
1482
|
-
previouslyHidden: scaleSpaceObstructed,
|
|
1483
|
-
})
|
|
1484
|
-
const left = Number.isFinite(scaleGeometry.left)
|
|
1485
|
-
? scaleGeometry.left
|
|
1486
|
-
: CONVERSATION_SCALE_CONTENT_GAP
|
|
1487
|
-
return { operationBox, scrollerBox, composerBox, top, height, left, scaleGeometry }
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
function boxesIntersect(left, right) {
|
|
1491
|
-
return left.right > right.left
|
|
1492
|
-
&& left.left < right.right
|
|
1493
|
-
&& left.bottom > right.top
|
|
1494
|
-
&& left.top < right.bottom
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function overlayIntersects(candidate, targets) {
|
|
1498
|
-
if (!candidate?.isConnected || candidate.hidden || candidate.getAttribute('aria-hidden') === 'true') return false
|
|
1499
|
-
if (candidate.closest?.('[data-prts-owned-conversation-scale], [data-prts-owned-conversation-preview]')) return false
|
|
1500
|
-
const style = window?.getComputedStyle?.(candidate)
|
|
1501
|
-
if (style?.display === 'none' || style?.visibility === 'hidden' || style?.opacity === '0') return false
|
|
1502
|
-
const box = candidate.getBoundingClientRect?.()
|
|
1503
|
-
if (!box || box.width <= 0 || box.height <= 0) return false
|
|
1504
|
-
return targets.some(target => target?.width > 0 && target?.height > 0 && boxesIntersect(box, target))
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
function blockingOverlay(candidate) {
|
|
1508
|
-
if (candidate?.matches?.(CONVERSATION_BLOCKING_OVERLAY_SELECTOR)) return true
|
|
1509
|
-
const plugin = candidate?.matches?.('[data-plugin]') ? candidate : candidate?.closest?.('[data-plugin]')
|
|
1510
|
-
return Boolean(plugin
|
|
1511
|
-
&& plugin.getAttribute('data-plugin') !== 'dsh-theme-prts'
|
|
1512
|
-
&& plugin.closest?.('[data-shell-overlay], [data-slot="shell.overlay"]'))
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
function syncScaleInteractiveState() {
|
|
1516
|
-
if (!scale) return true
|
|
1517
|
-
const hidden = scaleSpaceObstructed || scaleBlockingObstructed || scaleObstructed || scaleItemCount() < MINIMUM_TURNS
|
|
1518
|
-
const wasHidden = scale.tabIndex === -1
|
|
1519
|
-
if (hidden && !wasHidden) {
|
|
1520
|
-
hidePreview()
|
|
1521
|
-
pointerInside = false
|
|
1522
|
-
hoverIndex = -1
|
|
1523
|
-
keyboardActive = false
|
|
1524
|
-
retargetWave(-1)
|
|
1525
|
-
scale.tabIndex = -1
|
|
1526
|
-
scale.setAttribute('aria-hidden', 'true')
|
|
1527
|
-
if (document.activeElement === scale) scale.blur?.()
|
|
1528
|
-
} else if (!hidden && wasHidden) {
|
|
1529
|
-
scale.tabIndex = 0
|
|
1530
|
-
scale.removeAttribute('aria-hidden')
|
|
1531
|
-
}
|
|
1532
|
-
syncHistoryHint()
|
|
1533
|
-
return hidden
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
function syncScaleSpaceObstruction(resolution) {
|
|
1537
|
-
const obstructed = !resolution.visible
|
|
1538
|
-
scaleSpaceObstructed = obstructed
|
|
1539
|
-
scale.toggleAttribute('data-prts-conversation-scale-space-obstructed', obstructed)
|
|
1540
|
-
if (obstructed) scale.dataset.prtsScaleHiddenReason = resolution.reason || 'space'
|
|
1541
|
-
else delete scale.dataset.prtsScaleHiddenReason
|
|
1542
|
-
scale.toggleAttribute('data-prts-conversation-scale-centered', Boolean(resolution.centered))
|
|
1543
|
-
syncScaleInteractiveState()
|
|
1544
|
-
return scaleSpaceObstructed || scaleItemCount() < MINIMUM_TURNS
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
function syncScaleObstruction(currentGeometry) {
|
|
1548
|
-
if (!scale) return false
|
|
1549
|
-
obstructionDirty = false
|
|
1550
|
-
if (scaleSpaceObstructed) {
|
|
1551
|
-
scaleBlockingObstructed = false
|
|
1552
|
-
scaleObstructed = false
|
|
1553
|
-
scale.removeAttribute('data-prts-conversation-scale-blocked')
|
|
1554
|
-
scale.removeAttribute('data-prts-conversation-scale-obstructed')
|
|
1555
|
-
syncScaleInteractiveState()
|
|
1556
|
-
return false
|
|
1557
|
-
}
|
|
1558
|
-
const candidates = [...overlayCandidates]
|
|
1559
|
-
const operationBox = currentGeometry?.operationBox
|
|
1560
|
-
const blocked = Boolean(operationBox?.width && operationBox?.height)
|
|
1561
|
-
&& candidates.some(candidate => blockingOverlay(candidate) && overlayIntersects(candidate, [operationBox]))
|
|
1562
|
-
const scaleBox = scale.getBoundingClientRect?.()
|
|
1563
|
-
const previewBox = preview?.hasAttribute?.('data-prts-conversation-preview-visible')
|
|
1564
|
-
? preview.getBoundingClientRect?.()
|
|
1565
|
-
: undefined
|
|
1566
|
-
const historyHintBox = historyHint && !historyHint.hidden
|
|
1567
|
-
? historyHint.getBoundingClientRect?.()
|
|
1568
|
-
: undefined
|
|
1569
|
-
const historyTooltipBox = scale._prtsHistoryTooltip && !scale._prtsHistoryTooltip.hidden
|
|
1570
|
-
? scale._prtsHistoryTooltip.getBoundingClientRect?.()
|
|
1571
|
-
: undefined
|
|
1572
|
-
const historyStatusBox = historyStatus && !historyStatus.hidden
|
|
1573
|
-
? historyStatus.getBoundingClientRect?.()
|
|
1574
|
-
: undefined
|
|
1575
|
-
const localTargets = [scaleBox, previewBox, historyHintBox, historyTooltipBox, historyStatusBox].filter(Boolean)
|
|
1576
|
-
const obstructed = !blocked
|
|
1577
|
-
&& localTargets.length > 0
|
|
1578
|
-
&& candidates.some(candidate => !blockingOverlay(candidate) && overlayIntersects(candidate, localTargets))
|
|
1579
|
-
scaleBlockingObstructed = blocked
|
|
1580
|
-
scaleObstructed = obstructed
|
|
1581
|
-
scale.toggleAttribute('data-prts-conversation-scale-blocked', blocked)
|
|
1582
|
-
scale.toggleAttribute('data-prts-conversation-scale-obstructed', obstructed)
|
|
1583
|
-
syncScaleInteractiveState()
|
|
1584
|
-
return blocked || obstructed
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
function positionPreview(index) {
|
|
1588
|
-
if (!preview || !layout || !geometry || index < 0 || index >= layout.count) return
|
|
1589
|
-
const scaleIndex = turnToScaleIndex(index)
|
|
1590
|
-
const tickY = clamp(layout.positions[scaleIndex], .5, layout.height - .5)
|
|
1591
|
-
const anchor = geometry.top + tickY
|
|
1592
|
-
const previewBox = preview.getBoundingClientRect?.()
|
|
1593
|
-
const measuredHeight = previewBox?.height || preview.offsetHeight || PREVIEW_FALLBACK_HEIGHT
|
|
1594
|
-
const maximumTop = Math.max(geometry.top, geometry.top + geometry.height - measuredHeight)
|
|
1595
|
-
const top = clamp(anchor - measuredHeight / 2, geometry.top, maximumTop)
|
|
1596
|
-
const left = geometry.left + SCALE_WIDTH + PREVIEW_GAP
|
|
1597
|
-
preview.style.left = `${Number(left.toFixed(2))}px`
|
|
1598
|
-
preview.style.top = `${Number(top.toFixed(2))}px`
|
|
1599
|
-
preview.style.setProperty('--prts-preview-anchor-y', `${Number(clamp(anchor - top, 1, measuredHeight - 1).toFixed(2))}px`)
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
function render() {
|
|
1603
|
-
if (!operation || !scroller || !scale) return
|
|
1604
|
-
geometry = visibleGeometry()
|
|
1605
|
-
scale.style.top = `${Number(geometry.top.toFixed(2))}px`
|
|
1606
|
-
scale.style.left = `${Number(geometry.left.toFixed(2))}px`
|
|
1607
|
-
scale.style.height = `${Number(geometry.height.toFixed(2))}px`
|
|
1608
|
-
historyStatus.style.top = `${Number(geometry.top.toFixed(2))}px`
|
|
1609
|
-
historyStatus.style.left = `${Number((geometry.left + SCALE_WIDTH + PREVIEW_GAP).toFixed(2))}px`
|
|
1610
|
-
scale.dataset.prtsScaleMaxDistance = String(maximumDistance)
|
|
1611
|
-
scale.dataset.prtsScaleFocusContrast = String(focusContrast)
|
|
1612
|
-
syncHistoryStatus()
|
|
1613
|
-
if (syncScaleSpaceObstruction(geometry.scaleGeometry)) return
|
|
1614
|
-
if (obstructionDirty) {
|
|
1615
|
-
if (syncScaleObstruction(geometry)) return
|
|
1616
|
-
} else if (scaleBlockingObstructed || scaleObstructed) {
|
|
1617
|
-
return
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
const provisional = getConversationScaleLayout(scaleItemCount(), geometry.height, scaleOffset)
|
|
1621
|
-
if (followBottom) scaleOffset = provisional.maxOffset
|
|
1622
|
-
layout = getConversationScaleLayout(scaleItemCount(), geometry.height, scaleOffset)
|
|
1623
|
-
scaleOffset = layout.offset
|
|
1624
|
-
previousMaximum = layout.maxOffset
|
|
1625
|
-
followBottom = false
|
|
1626
|
-
const historyY = layout.positions[0] ?? .5
|
|
1627
|
-
historyHint.style.top = `${Number((geometry.top + historyY).toFixed(2))}px`
|
|
1628
|
-
historyHint.style.left = `${Number(geometry.left.toFixed(2))}px`
|
|
1629
|
-
syncHistoryHint()
|
|
1630
|
-
const historyAnchor = geometry.top + historyY
|
|
1631
|
-
const historyTooltipTop = clamp(
|
|
1632
|
-
historyAnchor - PREVIEW_FALLBACK_HEIGHT / 2,
|
|
1633
|
-
geometry.top,
|
|
1634
|
-
Math.max(geometry.top, geometry.top + geometry.height - PREVIEW_FALLBACK_HEIGHT),
|
|
1635
|
-
)
|
|
1636
|
-
const historyTooltip = scale._prtsHistoryTooltip
|
|
1637
|
-
historyTooltip.style.setProperty('--prts-history-tooltip-top', `${Number((historyTooltipTop - historyAnchor + HISTORY_TRIGGER_HEIGHT / 2).toFixed(2))}px`)
|
|
1638
|
-
historyTooltip.style.setProperty('--prts-history-tooltip-anchor-y', `${Number(clamp(historyAnchor - historyTooltipTop, 1, PREVIEW_FALLBACK_HEIGHT - 1).toFixed(2))}px`)
|
|
1639
|
-
|
|
1640
|
-
const interactionIndex = hoverIndex >= 0
|
|
1641
|
-
? hoverIndex
|
|
1642
|
-
: keyboardActive && scale.matches(':focus') ? cursorIndex : -1
|
|
1643
|
-
const paths = buildConversationScalePaths(layout, interactionIndex, {
|
|
1644
|
-
center: waveCenter,
|
|
1645
|
-
intensity: waveIntensity,
|
|
1646
|
-
historyAvailable,
|
|
1647
|
-
}, scaleLengths)
|
|
1648
|
-
scale._prtsSvg.setAttribute('viewBox', `0 0 ${SCALE_WIDTH} ${layout.height}`)
|
|
1649
|
-
scale._prtsGray.setAttribute('d', paths.gray)
|
|
1650
|
-
scale._prtsAccent.setAttribute('d', paths.yellow)
|
|
1651
|
-
scale._prtsHistory.setAttribute('d', paths.history)
|
|
1652
|
-
scale._prtsHit.setAttribute('height', String(layout.height))
|
|
1653
|
-
scale._prtsMask.setAttribute('height', String(layout.height))
|
|
1654
|
-
scale._prtsMaskRect.setAttribute('height', String(layout.height))
|
|
1655
|
-
if (scaleOffset > 0) {
|
|
1656
|
-
scale._prtsMarks.setAttribute('mask', `url(#${scale._prtsMaskId})`)
|
|
1657
|
-
scale.setAttribute('data-prts-conversation-scale-top-fade', '')
|
|
1658
|
-
} else {
|
|
1659
|
-
scale._prtsMarks.removeAttribute('mask')
|
|
1660
|
-
scale.removeAttribute('data-prts-conversation-scale-top-fade')
|
|
1661
|
-
}
|
|
1662
|
-
scale.dataset.prtsScaleCount = String(turns.length)
|
|
1663
|
-
scale.toggleAttribute('data-prts-conversation-scale-partial', historyAvailable)
|
|
1664
|
-
scale.dataset.prtsScaleOffset = String(Number(scaleOffset.toFixed(2)))
|
|
1665
|
-
scale.dataset.prtsScaleMaximum = String(Number(layout.maxOffset.toFixed(2)))
|
|
1666
|
-
if (interactionIndex >= 0) scale.dataset.prtsScaleWave = String(interactionIndex + 1)
|
|
1667
|
-
else delete scale.dataset.prtsScaleWave
|
|
1668
|
-
if (previewIndex >= 0) positionPreview(previewIndex)
|
|
1669
|
-
syncAccessibility()
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
function syncAccessibility() {
|
|
1673
|
-
if (!scale) return
|
|
1674
|
-
const maximum = Math.max(1, scaleItemCount())
|
|
1675
|
-
const current = clamp(cursorIndex + 1, 1, maximum)
|
|
1676
|
-
scale.setAttribute('aria-valuemin', '1')
|
|
1677
|
-
scale.setAttribute('aria-valuemax', String(maximum))
|
|
1678
|
-
scale.setAttribute('aria-valuenow', String(current))
|
|
1679
|
-
scale.setAttribute(
|
|
1680
|
-
'aria-valuetext',
|
|
1681
|
-
historyAvailable && cursorIndex === 0
|
|
1682
|
-
? '更早内容尚未载入,按回车前往会话开头'
|
|
1683
|
-
: turns.length
|
|
1684
|
-
? `当前已载入第 ${scaleToTurnIndex(cursorIndex) + 1} 轮,共 ${turns.length} 轮${historyAvailable ? ',前方仍有更早内容' : ''}`
|
|
1685
|
-
: '无可用会话刻度',
|
|
1686
|
-
)
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
function flush() {
|
|
1690
|
-
frame = undefined
|
|
1691
|
-
if (!started || failed) return
|
|
1692
|
-
try {
|
|
1693
|
-
if (!resolveRoots()) return
|
|
1694
|
-
readHistoryState()
|
|
1695
|
-
if (scanPending) {
|
|
1696
|
-
scanPending = false
|
|
1697
|
-
const mutations = pendingMutations
|
|
1698
|
-
const full = fullScanPending
|
|
1699
|
-
pendingMutations = []
|
|
1700
|
-
fullScanPending = false
|
|
1701
|
-
syncTurns(mutations, full)
|
|
1702
|
-
}
|
|
1703
|
-
render()
|
|
1704
|
-
} catch {
|
|
1705
|
-
failed = true
|
|
1706
|
-
disconnectRoots()
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
function localIndex(event) {
|
|
1711
|
-
if (!layout || scaleItemCount() < MINIMUM_TURNS) return -1
|
|
1712
|
-
const box = scale.getBoundingClientRect()
|
|
1713
|
-
return nearestVisibleConversationTick(layout, event.clientY - box.top)
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
function ensureVisible(index) {
|
|
1717
|
-
if (!layout || index < 0) return
|
|
1718
|
-
const target = layout.start + index * CONVERSATION_SCALE_SPACING - scaleOffset
|
|
1719
|
-
if (target < .5) scaleOffset += target - .5
|
|
1720
|
-
else if (target > layout.height - .5) scaleOffset += target - (layout.height - .5)
|
|
1721
|
-
scaleOffset = clamp(scaleOffset, 0, layout.maxOffset)
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
function animatePreviewContent() {
|
|
1725
|
-
if (reducedMotion() || typeof preview?._prtsBody?.animate !== 'function') return
|
|
1726
|
-
preview._prtsBody.animate([
|
|
1727
|
-
{ opacity: .28 },
|
|
1728
|
-
{ opacity: 1 },
|
|
1729
|
-
], { duration: 70, easing: 'ease-out' })
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
function updatePreviewContent(index, animate = false) {
|
|
1733
|
-
if (!preview || index < 0 || index >= turns.length) return
|
|
1734
|
-
const content = getConversationTurnPreview(scroller, turns, index)
|
|
1735
|
-
const changed = preview._prtsQuestion.textContent !== content.question
|
|
1736
|
-
|| preview._prtsAnswer.textContent !== content.answer
|
|
1737
|
-
preview._prtsQuestion.textContent = content.question
|
|
1738
|
-
preview._prtsAnswer.textContent = content.answer
|
|
1739
|
-
preview.dataset.prtsPreviewState = content.state
|
|
1740
|
-
preview.dataset.prtsPreviewTurn = String(index + 1)
|
|
1741
|
-
observePreviewSource(content.answerSource)
|
|
1742
|
-
if (changed && animate && preview.hasAttribute('data-prts-conversation-preview-visible')) animatePreviewContent()
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
function showPreview(index, { animate = true, temporary = false } = {}) {
|
|
1746
|
-
if (!preview || index < 0 || index >= turns.length) return
|
|
1747
|
-
if (previewDelayTimer !== undefined) window?.clearTimeout?.(previewDelayTimer)
|
|
1748
|
-
if (previewHideTimer !== undefined) window?.clearTimeout?.(previewHideTimer)
|
|
1749
|
-
previewDelayTimer = undefined
|
|
1750
|
-
previewHideTimer = undefined
|
|
1751
|
-
pendingPreviewIndex = -1
|
|
1752
|
-
const switched = previewIndex !== index
|
|
1753
|
-
previewIndex = index
|
|
1754
|
-
updatePreviewContent(index, animate && switched)
|
|
1755
|
-
preview.setAttribute('data-prts-conversation-preview-visible', '')
|
|
1756
|
-
preview.setAttribute('aria-hidden', 'false')
|
|
1757
|
-
scale?.setAttribute('aria-describedby', preview.id)
|
|
1758
|
-
positionPreview(index)
|
|
1759
|
-
if (temporary) {
|
|
1760
|
-
previewHideTimer = window?.setTimeout?.(() => {
|
|
1761
|
-
previewHideTimer = undefined
|
|
1762
|
-
hidePreview()
|
|
1763
|
-
}, PREVIEW_TOUCH_DURATION)
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
function queuePreview(index) {
|
|
1768
|
-
if (index < 0 || index >= turns.length) return
|
|
1769
|
-
if (preview?.hasAttribute('data-prts-conversation-preview-visible')) {
|
|
1770
|
-
showPreview(index)
|
|
1771
|
-
return
|
|
1772
|
-
}
|
|
1773
|
-
if (pendingPreviewIndex === index && previewDelayTimer !== undefined) return
|
|
1774
|
-
if (previewDelayTimer !== undefined) window?.clearTimeout?.(previewDelayTimer)
|
|
1775
|
-
pendingPreviewIndex = index
|
|
1776
|
-
previewDelayTimer = window?.setTimeout?.(() => {
|
|
1777
|
-
previewDelayTimer = undefined
|
|
1778
|
-
const pending = pendingPreviewIndex
|
|
1779
|
-
pendingPreviewIndex = -1
|
|
1780
|
-
if (pointerInside && scaleToTurnIndex(hoverIndex) === pending) showPreview(pending)
|
|
1781
|
-
}, PREVIEW_DELAY)
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
function hidePreview() {
|
|
1785
|
-
if (previewDelayTimer !== undefined) window?.clearTimeout?.(previewDelayTimer)
|
|
1786
|
-
if (previewHideTimer !== undefined) window?.clearTimeout?.(previewHideTimer)
|
|
1787
|
-
if (previewRefreshTimer !== undefined) window?.clearTimeout?.(previewRefreshTimer)
|
|
1788
|
-
previewDelayTimer = undefined
|
|
1789
|
-
previewHideTimer = undefined
|
|
1790
|
-
previewRefreshTimer = undefined
|
|
1791
|
-
pendingPreviewIndex = -1
|
|
1792
|
-
previewIndex = -1
|
|
1793
|
-
disconnectPreviewObserver()
|
|
1794
|
-
preview?.removeAttribute('data-prts-conversation-preview-visible')
|
|
1795
|
-
preview?.setAttribute('aria-hidden', 'true')
|
|
1796
|
-
scale?.removeAttribute('aria-describedby')
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
function queuePreviewRefresh() {
|
|
1800
|
-
if (previewIndex < 0 || previewRefreshTimer !== undefined) return
|
|
1801
|
-
previewRefreshTimer = window?.setTimeout?.(() => {
|
|
1802
|
-
previewRefreshTimer = undefined
|
|
1803
|
-
if (previewIndex >= 0) {
|
|
1804
|
-
updatePreviewContent(previewIndex, true)
|
|
1805
|
-
positionPreview(previewIndex)
|
|
1806
|
-
}
|
|
1807
|
-
}, PREVIEW_REFRESH_INTERVAL)
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
function wait(milliseconds) {
|
|
1811
|
-
return new Promise(resolve => window?.setTimeout?.(resolve, milliseconds))
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
async function waitForHistoryPage(request, before, deadline) {
|
|
1815
|
-
let sawLoading = false
|
|
1816
|
-
while (Date.now() < deadline) {
|
|
1817
|
-
await wait(40)
|
|
1818
|
-
if (request !== historyRequest) return 'cancelled'
|
|
1819
|
-
const state = readHistoryState()
|
|
1820
|
-
sawLoading ||= state.loading || Boolean(state.olderButton?.disabled)
|
|
1821
|
-
const firstTurn = collectConversationTurns(scroller)[0]
|
|
1822
|
-
const progressed = historyMutationRevision > before.revision
|
|
1823
|
-
&& (firstTurn !== before.firstTurn || !state.loading)
|
|
1824
|
-
if (!state.hasMore && !state.olderButton && !state.loading) return 'complete'
|
|
1825
|
-
if (progressed && !state.loading && !state.olderButton?.disabled) return 'progress'
|
|
1826
|
-
if (sawLoading && !state.loading && !state.olderButton?.disabled) return 'progress'
|
|
1827
|
-
}
|
|
1828
|
-
return 'timeout'
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
async function loadHistoryToStart() {
|
|
1832
|
-
if (!operation || !scroller || historyPhase === 'loading') return
|
|
1833
|
-
const initial = readHistoryState()
|
|
1834
|
-
if (!historyAvailable) {
|
|
1835
|
-
if (turns[0]) jump(0)
|
|
1836
|
-
return
|
|
1837
|
-
}
|
|
1838
|
-
const request = historyRequest + 1
|
|
1839
|
-
historyRequest = request
|
|
1840
|
-
const startedAt = Date.now()
|
|
1841
|
-
const sessionKey = initial.key
|
|
1842
|
-
let loadedThisRun = 0
|
|
1843
|
-
historyPhase = 'loading'
|
|
1844
|
-
historyMessage = ''
|
|
1845
|
-
hidePreview()
|
|
1846
|
-
syncHistoryStatus()
|
|
1847
|
-
schedule()
|
|
1848
|
-
|
|
1849
|
-
while (loadedThisRun < CONVERSATION_HISTORY_PAGE_LIMIT
|
|
1850
|
-
&& Date.now() - startedAt < CONVERSATION_HISTORY_TIME_LIMIT) {
|
|
1851
|
-
if (request !== historyRequest) return
|
|
1852
|
-
const state = readHistoryState()
|
|
1853
|
-
if (sessionKey !== undefined && state.key !== undefined && state.key !== sessionKey) {
|
|
1854
|
-
cancelHistory({ paused: false })
|
|
1855
|
-
return
|
|
1856
|
-
}
|
|
1857
|
-
if (!state.hasMore && !state.olderButton && !state.loading) {
|
|
1858
|
-
setHistoryAvailable(false)
|
|
1859
|
-
historyPhase = 'idle'
|
|
1860
|
-
syncTurns([], true)
|
|
1861
|
-
schedule()
|
|
1862
|
-
if (turns[0]) jump(0)
|
|
1863
|
-
return
|
|
1864
|
-
}
|
|
1865
|
-
if (state.loading || state.olderButton?.disabled) {
|
|
1866
|
-
const result = await waitForHistoryPage(
|
|
1867
|
-
request,
|
|
1868
|
-
{
|
|
1869
|
-
firstTurn: collectConversationTurns(scroller)[0],
|
|
1870
|
-
revision: historyMutationRevision,
|
|
1871
|
-
},
|
|
1872
|
-
Math.min(startedAt + CONVERSATION_HISTORY_TIME_LIMIT, Date.now() + HISTORY_PAGE_SETTLE_LIMIT),
|
|
1873
|
-
)
|
|
1874
|
-
if (result === 'cancelled') return
|
|
1875
|
-
if (result === 'timeout') {
|
|
1876
|
-
historyPhase = 'error'
|
|
1877
|
-
historyMessage = '载入超时,可继续重试'
|
|
1878
|
-
break
|
|
1879
|
-
}
|
|
1880
|
-
historyPages += 1
|
|
1881
|
-
loadedThisRun += 1
|
|
1882
|
-
syncTurns([], true)
|
|
1883
|
-
syncHistoryStatus()
|
|
1884
|
-
schedule()
|
|
1885
|
-
continue
|
|
1886
|
-
}
|
|
1887
|
-
if (!state.olderButton) {
|
|
1888
|
-
const availableDeadline = Math.min(startedAt + CONVERSATION_HISTORY_TIME_LIMIT, Date.now() + 600)
|
|
1889
|
-
while (request === historyRequest && Date.now() < availableDeadline && !findConversationOlderButton(operation)) {
|
|
1890
|
-
await wait(40)
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
const button = findConversationOlderButton(operation)
|
|
1894
|
-
if (!button || button.disabled) {
|
|
1895
|
-
historyPhase = 'error'
|
|
1896
|
-
historyMessage = '暂时无法载入更早内容'
|
|
1897
|
-
break
|
|
1898
|
-
}
|
|
1899
|
-
const before = {
|
|
1900
|
-
firstTurn: collectConversationTurns(scroller)[0],
|
|
1901
|
-
revision: historyMutationRevision,
|
|
1902
|
-
}
|
|
1903
|
-
button.click()
|
|
1904
|
-
const result = await waitForHistoryPage(
|
|
1905
|
-
request,
|
|
1906
|
-
before,
|
|
1907
|
-
Math.min(startedAt + CONVERSATION_HISTORY_TIME_LIMIT, Date.now() + HISTORY_PAGE_SETTLE_LIMIT),
|
|
1908
|
-
)
|
|
1909
|
-
if (result === 'cancelled') return
|
|
1910
|
-
if (result === 'timeout') {
|
|
1911
|
-
historyPhase = 'error'
|
|
1912
|
-
historyMessage = '载入超时,可继续重试'
|
|
1913
|
-
break
|
|
1914
|
-
}
|
|
1915
|
-
historyPages += 1
|
|
1916
|
-
loadedThisRun += 1
|
|
1917
|
-
syncTurns([], true)
|
|
1918
|
-
syncHistoryStatus()
|
|
1919
|
-
schedule()
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
if (request !== historyRequest) return
|
|
1923
|
-
const finalState = readHistoryState()
|
|
1924
|
-
if (!finalState.hasMore && !finalState.olderButton && !finalState.loading) {
|
|
1925
|
-
setHistoryAvailable(false)
|
|
1926
|
-
historyPhase = 'idle'
|
|
1927
|
-
syncTurns([], true)
|
|
1928
|
-
if (turns[0]) jump(0)
|
|
1929
|
-
} else if (historyPhase === 'loading') {
|
|
1930
|
-
historyPhase = 'paused'
|
|
1931
|
-
}
|
|
1932
|
-
syncHistoryStatus()
|
|
1933
|
-
schedule()
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
function jump(index) {
|
|
1937
|
-
const turn = turns[index]
|
|
1938
|
-
if (!turn || !scroller || !operation) return
|
|
1939
|
-
const jumpScroller = scroller
|
|
1940
|
-
const jumpOperation = operation
|
|
1941
|
-
const jumpComposer = composer
|
|
1942
|
-
const request = jumpRequest + 1
|
|
1943
|
-
jumpRequest = request
|
|
1944
|
-
const getTargetTop = () => {
|
|
1945
|
-
if (!turn.isConnected || scroller !== jumpScroller || operation !== jumpOperation) return Number.NaN
|
|
1946
|
-
const operationBox = jumpOperation.getBoundingClientRect()
|
|
1947
|
-
const scrollerBox = jumpScroller.getBoundingClientRect()
|
|
1948
|
-
const composerBox = jumpComposer?.getBoundingClientRect?.()
|
|
1949
|
-
const visibleTop = Math.max(scrollerBox.top, operationBox.top)
|
|
1950
|
-
const visibleBottom = Math.min(scrollerBox.bottom, operationBox.bottom, composerBox?.top ?? operationBox.bottom)
|
|
1951
|
-
return calculateConversationJumpTop({
|
|
1952
|
-
scrollTop: jumpScroller.scrollTop,
|
|
1953
|
-
scrollHeight: jumpScroller.scrollHeight,
|
|
1954
|
-
clientHeight: jumpScroller.clientHeight,
|
|
1955
|
-
turnTop: turn.getBoundingClientRect().top,
|
|
1956
|
-
visibleTop,
|
|
1957
|
-
visibleHeight: Math.max(0, visibleBottom - visibleTop),
|
|
1958
|
-
})
|
|
1959
|
-
}
|
|
1960
|
-
void performConversationJump({
|
|
1961
|
-
scroller: jumpScroller,
|
|
1962
|
-
getTargetTop,
|
|
1963
|
-
prefersReducedMotion: reducedMotion(),
|
|
1964
|
-
requestFrame,
|
|
1965
|
-
now: () => currentTime(),
|
|
1966
|
-
cancelled: () => request !== jumpRequest,
|
|
1967
|
-
onFrame: schedule,
|
|
1968
|
-
}).catch(() => {
|
|
1969
|
-
if (request === jumpRequest) cancelJump()
|
|
1970
|
-
})
|
|
1971
|
-
cursorIndex = turnToScaleIndex(index)
|
|
1972
|
-
syncAccessibility()
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
function showScalePreview(index, options) {
|
|
1976
|
-
const turnIndex = scaleToTurnIndex(index)
|
|
1977
|
-
if (turnIndex < 0) {
|
|
1978
|
-
hidePreview()
|
|
1979
|
-
return
|
|
1980
|
-
}
|
|
1981
|
-
showPreview(turnIndex, options)
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
function queueScalePreview(index) {
|
|
1985
|
-
const turnIndex = scaleToTurnIndex(index)
|
|
1986
|
-
if (turnIndex < 0) {
|
|
1987
|
-
hidePreview()
|
|
1988
|
-
return
|
|
1989
|
-
}
|
|
1990
|
-
queuePreview(turnIndex)
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
function activateScaleIndex(index, options) {
|
|
1994
|
-
if (historyAvailable && index === 0) {
|
|
1995
|
-
hidePreview()
|
|
1996
|
-
void loadHistoryToStart()
|
|
1997
|
-
return
|
|
1998
|
-
}
|
|
1999
|
-
const turnIndex = scaleToTurnIndex(index)
|
|
2000
|
-
if (turnIndex < 0) return
|
|
2001
|
-
showPreview(turnIndex, options)
|
|
2002
|
-
jump(turnIndex)
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
function showWave(index, temporary = false) {
|
|
2006
|
-
if (index < 0) return
|
|
2007
|
-
hoverIndex = index
|
|
2008
|
-
cursorIndex = index
|
|
2009
|
-
ensureVisible(index)
|
|
2010
|
-
retargetWave(index)
|
|
2011
|
-
if (touchTimer !== undefined) window?.clearTimeout?.(touchTimer)
|
|
2012
|
-
if (temporary) {
|
|
2013
|
-
touchTimer = window?.setTimeout?.(() => {
|
|
2014
|
-
touchTimer = undefined
|
|
2015
|
-
hoverIndex = -1
|
|
2016
|
-
retargetWave(-1)
|
|
2017
|
-
schedule()
|
|
2018
|
-
}, TOUCH_WAVE_DURATION)
|
|
2019
|
-
}
|
|
2020
|
-
schedule()
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
function onPointerMove(event) {
|
|
2024
|
-
if (event.pointerType === 'touch') return
|
|
2025
|
-
pointerInside = true
|
|
2026
|
-
keyboardActive = false
|
|
2027
|
-
const index = localIndex(event)
|
|
2028
|
-
if (index === hoverIndex) return
|
|
2029
|
-
showWave(index)
|
|
2030
|
-
queueScalePreview(index)
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
function onPointerLeave(event) {
|
|
2034
|
-
if (event.pointerType === 'touch') return
|
|
2035
|
-
pointerInside = false
|
|
2036
|
-
hoverIndex = -1
|
|
2037
|
-
retargetWave(-1)
|
|
2038
|
-
hidePreview()
|
|
2039
|
-
schedule()
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
function onPointerDown(event) {
|
|
2043
|
-
cancelNavigation()
|
|
2044
|
-
if (event.pointerType !== 'touch' && event.pointerType !== 'pen') return
|
|
2045
|
-
pointerInside = true
|
|
2046
|
-
pointerStart = { x: event.clientX, y: event.clientY, id: event.pointerId }
|
|
2047
|
-
scale.setPointerCapture?.(event.pointerId)
|
|
2048
|
-
event.preventDefault()
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
function onPointerUp(event) {
|
|
2052
|
-
if (!pointerStart || pointerStart.id !== event.pointerId) return
|
|
2053
|
-
const distance = Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y)
|
|
2054
|
-
pointerStart = undefined
|
|
2055
|
-
if (event.pointerType === 'touch') pointerInside = false
|
|
2056
|
-
scale.releasePointerCapture?.(event.pointerId)
|
|
2057
|
-
if (distance > 8) return
|
|
2058
|
-
const index = localIndex(event)
|
|
2059
|
-
if (index < 0) return
|
|
2060
|
-
lastDirectJump = Date.now()
|
|
2061
|
-
keyboardActive = false
|
|
2062
|
-
showWave(index, true)
|
|
2063
|
-
activateScaleIndex(index, { temporary: true })
|
|
2064
|
-
scale.focus({ preventScroll: true })
|
|
2065
|
-
event.preventDefault()
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
function onPointerCancel(event) {
|
|
2069
|
-
if (pointerStart?.id === event.pointerId) {
|
|
2070
|
-
pointerStart = undefined
|
|
2071
|
-
if (event.pointerType === 'touch') pointerInside = false
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function onClick(event) {
|
|
2076
|
-
if (Date.now() - lastDirectJump < 300) return
|
|
2077
|
-
keyboardActive = false
|
|
2078
|
-
const index = localIndex(event)
|
|
2079
|
-
if (index < 0) return
|
|
2080
|
-
showWave(index)
|
|
2081
|
-
activateScaleIndex(index)
|
|
2082
|
-
scale.focus({ preventScroll: true })
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
function queueSnap() {
|
|
2086
|
-
if (snapTimer !== undefined) window?.clearTimeout?.(snapTimer)
|
|
2087
|
-
snapTimer = window?.setTimeout?.(() => {
|
|
2088
|
-
snapTimer = undefined
|
|
2089
|
-
if (!layout) return
|
|
2090
|
-
scaleOffset = snapConversationScaleOffset(scaleOffset, layout.maxOffset)
|
|
2091
|
-
schedule()
|
|
2092
|
-
}, SNAP_DELAY)
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
function onWheel(event) {
|
|
2096
|
-
cancelNavigation()
|
|
2097
|
-
event.preventDefault()
|
|
2098
|
-
event.stopPropagation()
|
|
2099
|
-
hidePreview()
|
|
2100
|
-
if (!layout || layout.maxOffset <= 0) return
|
|
2101
|
-
const factor = event.deltaMode === 1 ? CONVERSATION_SCALE_SPACING : event.deltaMode === 2 ? layout.height : 1
|
|
2102
|
-
scaleOffset = clamp(scaleOffset + event.deltaY * factor, 0, layout.maxOffset)
|
|
2103
|
-
keyboardActive = false
|
|
2104
|
-
hoverIndex = -1
|
|
2105
|
-
retargetWave(-1)
|
|
2106
|
-
schedule()
|
|
2107
|
-
queueSnap()
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
function page(direction) {
|
|
2111
|
-
if (!layout) return
|
|
2112
|
-
const distance = Math.max(CONVERSATION_SCALE_SPACING, Math.floor(layout.height / CONVERSATION_SCALE_SPACING) * CONVERSATION_SCALE_SPACING)
|
|
2113
|
-
scaleOffset = clamp(scaleOffset + direction * distance, 0, layout.maxOffset)
|
|
2114
|
-
const edgeY = direction < 0 ? .5 : layout.height - .5
|
|
2115
|
-
const nextLayout = getConversationScaleLayout(scaleItemCount(), layout.height, scaleOffset)
|
|
2116
|
-
cursorIndex = nearestVisibleConversationTick(nextLayout, edgeY)
|
|
2117
|
-
hoverIndex = cursorIndex
|
|
2118
|
-
retargetWave(cursorIndex)
|
|
2119
|
-
showScalePreview(cursorIndex)
|
|
2120
|
-
schedule()
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function onKeydown(event) {
|
|
2124
|
-
if (scaleItemCount() < MINIMUM_TURNS) return
|
|
2125
|
-
if (event.key === 'Escape' && historyPhase === 'loading') {
|
|
2126
|
-
cancelHistory()
|
|
2127
|
-
event.preventDefault()
|
|
2128
|
-
return
|
|
2129
|
-
}
|
|
2130
|
-
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
|
|
2131
|
-
keyboardActive = true
|
|
2132
|
-
cursorIndex = clamp(cursorIndex + (event.key === 'ArrowUp' ? -1 : 1), 0, scaleItemCount() - 1)
|
|
2133
|
-
hoverIndex = cursorIndex
|
|
2134
|
-
ensureVisible(cursorIndex)
|
|
2135
|
-
showScalePreview(cursorIndex)
|
|
2136
|
-
} else if (event.key === 'Home' || event.key === 'End') {
|
|
2137
|
-
keyboardActive = true
|
|
2138
|
-
cursorIndex = event.key === 'Home' ? 0 : scaleItemCount() - 1
|
|
2139
|
-
hoverIndex = cursorIndex
|
|
2140
|
-
ensureVisible(cursorIndex)
|
|
2141
|
-
showScalePreview(cursorIndex)
|
|
2142
|
-
} else if (event.key === 'PageUp' || event.key === 'PageDown') {
|
|
2143
|
-
keyboardActive = true
|
|
2144
|
-
page(event.key === 'PageUp' ? -1 : 1)
|
|
2145
|
-
event.preventDefault()
|
|
2146
|
-
return
|
|
2147
|
-
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
2148
|
-
event.preventDefault()
|
|
2149
|
-
activateScaleIndex(cursorIndex)
|
|
2150
|
-
return
|
|
2151
|
-
} else return
|
|
2152
|
-
event.preventDefault()
|
|
2153
|
-
retargetWave(cursorIndex)
|
|
2154
|
-
schedule()
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
function onFocus() {
|
|
2158
|
-
ensureVisible(cursorIndex)
|
|
2159
|
-
schedule()
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
function onBlur() {
|
|
2163
|
-
keyboardActive = false
|
|
2164
|
-
hoverIndex = -1
|
|
2165
|
-
retargetWave(-1)
|
|
2166
|
-
if (!pointerInside) hidePreview()
|
|
2167
|
-
schedule()
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
return {
|
|
2171
|
-
getCalibrationState(candidate = maximumDistance) {
|
|
2172
|
-
if (!started || failed || (!operation && !resolveRoots())) {
|
|
2173
|
-
const fallback = resolveConversationScaleCalibrationState({
|
|
2174
|
-
maximumDistance: candidate,
|
|
2175
|
-
viewportWidth: window?.innerWidth,
|
|
2176
|
-
})
|
|
2177
|
-
return { ...fallback, simulated: true }
|
|
2178
|
-
}
|
|
2179
|
-
const cachedOperationBox = geometry?.operationBox
|
|
2180
|
-
const operationBox = cachedOperationBox ?? operation.getBoundingClientRect?.()
|
|
2181
|
-
const cachedContentLeft = Number.isFinite(contentLeft) ? contentLeft : undefined
|
|
2182
|
-
return resolveConversationScaleCalibrationState({
|
|
2183
|
-
operationWidth: operationBox?.width,
|
|
2184
|
-
contentLeft: cachedContentLeft ?? representativeContentLeft(operationBox),
|
|
2185
|
-
maximumDistance: candidate,
|
|
2186
|
-
turnCount: turns.length,
|
|
2187
|
-
drawerMode: document.documentElement.dataset.prtsDrawerMode,
|
|
2188
|
-
viewportWidth: window?.innerWidth,
|
|
2189
|
-
previouslyHidden: scaleSpaceObstructed,
|
|
2190
|
-
})
|
|
2191
|
-
},
|
|
2192
|
-
update(nextPreferences = {}) {
|
|
2193
|
-
const distanceNumeric = Number(nextPreferences.conversationScaleMaxDistance)
|
|
2194
|
-
const nextDistance = Number.isFinite(distanceNumeric)
|
|
2195
|
-
? clamp(Math.round(distanceNumeric / 8) * 8, 16, 240)
|
|
2196
|
-
: CONVERSATION_SCALE_DEFAULT_MAX_DISTANCE
|
|
2197
|
-
const contrastNumeric = Number(nextPreferences.conversationScaleFocusContrast)
|
|
2198
|
-
const nextContrast = Number.isFinite(contrastNumeric)
|
|
2199
|
-
? clamp(Math.round(contrastNumeric / 10) * 10, 0, 100)
|
|
2200
|
-
: CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT
|
|
2201
|
-
if (nextDistance === maximumDistance && nextContrast === focusContrast) return
|
|
2202
|
-
maximumDistance = nextDistance
|
|
2203
|
-
if (nextContrast !== focusContrast) {
|
|
2204
|
-
focusContrast = nextContrast
|
|
2205
|
-
scaleLengths = resolveConversationScaleLengths(focusContrast)
|
|
2206
|
-
}
|
|
2207
|
-
schedule()
|
|
2208
|
-
},
|
|
2209
|
-
start() {
|
|
2210
|
-
if (started || !document) return
|
|
2211
|
-
started = true
|
|
2212
|
-
failed = false
|
|
2213
|
-
window?.addEventListener?.('resize', onHostGeometryChange)
|
|
2214
|
-
window?.addEventListener?.('orientationchange', onHostGeometryChange)
|
|
2215
|
-
window?.addEventListener?.('pageshow', onHostGeometryChange)
|
|
2216
|
-
window?.visualViewport?.addEventListener?.('resize', onHostGeometryChange)
|
|
2217
|
-
const ResizeObserver = window?.ResizeObserver
|
|
2218
|
-
if (typeof ResizeObserver === 'function') {
|
|
2219
|
-
resizeObserver = new ResizeObserver((entries = []) => {
|
|
2220
|
-
geometryDirty = true
|
|
2221
|
-
if (phoneScaleHidden()) {
|
|
2222
|
-
stopGeometryStabilization()
|
|
2223
|
-
if (resizeSettleTimer !== undefined) window?.clearTimeout?.(resizeSettleTimer)
|
|
2224
|
-
resizeSettleTimer = undefined
|
|
2225
|
-
setScaleResizeState(false)
|
|
2226
|
-
return
|
|
2227
|
-
}
|
|
2228
|
-
requestGeometryStabilization()
|
|
2229
|
-
const operationEntry = entries.find(entry => entry.target === operation)
|
|
2230
|
-
const width = Number(operationEntry?.contentRect?.width)
|
|
2231
|
-
const height = Number(operationEntry?.contentRect?.height)
|
|
2232
|
-
const operationChanged = !entries.length || Boolean(operationEntry) && (
|
|
2233
|
-
!Number.isFinite(width)
|
|
2234
|
-
|| !Number.isFinite(height)
|
|
2235
|
-
|| width !== observedOperationWidth
|
|
2236
|
-
|| height !== observedOperationHeight
|
|
2237
|
-
)
|
|
2238
|
-
if (operationEntry) {
|
|
2239
|
-
if (Number.isFinite(width)) observedOperationWidth = width
|
|
2240
|
-
if (Number.isFinite(height)) observedOperationHeight = height
|
|
2241
|
-
}
|
|
2242
|
-
if (!operationChanged) {
|
|
2243
|
-
schedule()
|
|
2244
|
-
return
|
|
2245
|
-
}
|
|
2246
|
-
if (!resizeActive) {
|
|
2247
|
-
schedule()
|
|
2248
|
-
setScaleResizeState(true)
|
|
2249
|
-
}
|
|
2250
|
-
if (resizeSettleTimer !== undefined) window?.clearTimeout?.(resizeSettleTimer)
|
|
2251
|
-
resizeSettleTimer = window?.setTimeout?.(() => {
|
|
2252
|
-
resizeSettleTimer = undefined
|
|
2253
|
-
obstructionDirty = true
|
|
2254
|
-
setScaleResizeState(false)
|
|
2255
|
-
schedule()
|
|
2256
|
-
}, 160)
|
|
2257
|
-
})
|
|
2258
|
-
}
|
|
2259
|
-
const MutationObserver = window?.MutationObserver
|
|
2260
|
-
if (typeof MutationObserver === 'function' && document.body) {
|
|
2261
|
-
mutationObserver = new MutationObserver(mutations => {
|
|
2262
|
-
const external = mutations.filter(mutation =>
|
|
2263
|
-
!mutation.target?.closest?.('[data-prts-conversation-scale], [data-prts-conversation-preview], [data-prts-conversation-history-hint], [data-prts-conversation-history-status]')
|
|
2264
|
-
)
|
|
2265
|
-
if (phoneScaleHidden()) return
|
|
2266
|
-
if (external.some(mutation =>
|
|
2267
|
-
operation?.contains?.(mutation.target)
|
|
2268
|
-
|| mutation.target?.contains?.(operation)
|
|
2269
|
-
|| mutation.target?.matches?.('header, [data-host-header], [data-slot*="header" i]')
|
|
2270
|
-
|| mutationAffectsOverlay(mutation)
|
|
2271
|
-
)) requestGeometryStabilization()
|
|
2272
|
-
const historyRelevant = external.some(mutation =>
|
|
2273
|
-
mutation.type === 'childList'
|
|
2274
|
-
&& operation?.contains?.(mutation.target)
|
|
2275
|
-
&& ((mutation.addedNodes?.length ?? 0) > 0 || (mutation.removedNodes?.length ?? 0) > 0)
|
|
2276
|
-
)
|
|
2277
|
-
if (historyRelevant) historyMutationRevision += 1
|
|
2278
|
-
const relevant = external.filter(mutation =>
|
|
2279
|
-
mutationAffectsTurns(mutation) || mutationAffectsOverlay(mutation)
|
|
2280
|
-
)
|
|
2281
|
-
if (relevant.some(mutationAffectsOverlay)) obstructionDirty = true
|
|
2282
|
-
if (!relevant.length && !historyRelevant) return
|
|
2283
|
-
if (previewIndex >= 0) queuePreviewRefresh()
|
|
2284
|
-
pendingMutations.push(...relevant)
|
|
2285
|
-
scanPending ||= relevant.length > 0
|
|
2286
|
-
schedule()
|
|
2287
|
-
})
|
|
2288
|
-
mutationObserver.observe(document.body, {
|
|
2289
|
-
subtree: true,
|
|
2290
|
-
childList: true,
|
|
2291
|
-
attributes: true,
|
|
2292
|
-
attributeFilter: [
|
|
2293
|
-
'data-chat-flow-kind',
|
|
2294
|
-
'data-message-role',
|
|
2295
|
-
'data-prts-floating-glass',
|
|
2296
|
-
'hidden',
|
|
2297
|
-
'aria-hidden',
|
|
2298
|
-
'aria-modal',
|
|
2299
|
-
'role',
|
|
2300
|
-
'open',
|
|
2301
|
-
'popover',
|
|
2302
|
-
'class',
|
|
2303
|
-
'data-slot',
|
|
2304
|
-
'data-plugin',
|
|
2305
|
-
'style',
|
|
2306
|
-
],
|
|
2307
|
-
})
|
|
2308
|
-
}
|
|
2309
|
-
document.fonts?.ready?.then?.(() => {
|
|
2310
|
-
if (started) requestGeometryStabilization()
|
|
2311
|
-
}).catch?.(() => {})
|
|
2312
|
-
schedule(true)
|
|
2313
|
-
},
|
|
2314
|
-
dispose() {
|
|
2315
|
-
window?.removeEventListener?.('resize', onHostGeometryChange)
|
|
2316
|
-
window?.removeEventListener?.('orientationchange', onHostGeometryChange)
|
|
2317
|
-
window?.removeEventListener?.('pageshow', onHostGeometryChange)
|
|
2318
|
-
window?.visualViewport?.removeEventListener?.('resize', onHostGeometryChange)
|
|
2319
|
-
stopGeometryStabilization()
|
|
2320
|
-
started = false
|
|
2321
|
-
failed = false
|
|
2322
|
-
mutationObserver?.disconnect()
|
|
2323
|
-
mutationObserver = undefined
|
|
2324
|
-
resizeObserver?.disconnect()
|
|
2325
|
-
resizeObserver = undefined
|
|
2326
|
-
disconnectPreviewObserver()
|
|
2327
|
-
if (frame !== undefined) {
|
|
2328
|
-
if (window?.cancelAnimationFrame) window.cancelAnimationFrame(frame)
|
|
2329
|
-
else window?.clearTimeout?.(frame)
|
|
2330
|
-
}
|
|
2331
|
-
frame = undefined
|
|
2332
|
-
scanPending = false
|
|
2333
|
-
fullScanPending = false
|
|
2334
|
-
pendingMutations = []
|
|
2335
|
-
overlayCandidates.clear()
|
|
2336
|
-
overlayScanPending = false
|
|
2337
|
-
scaleObstructed = false
|
|
2338
|
-
scaleBlockingObstructed = false
|
|
2339
|
-
scaleSpaceObstructed = true
|
|
2340
|
-
contentLeft = undefined
|
|
2341
|
-
geometryDirty = true
|
|
2342
|
-
clearTimers()
|
|
2343
|
-
setScaleResizeState(false)
|
|
2344
|
-
disconnectRoots()
|
|
2345
|
-
},
|
|
2346
|
-
}
|
|
2347
|
-
}
|