@ash-qw/dsh-theme-prts 1.3.0 → 1.4.0
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 +20 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/THIRD_PARTY_NOTICES.zh-CN.md +7 -0
- package/lib/client.js +882 -41
- package/package.json +3 -2
- package/src/client/assistant-glass-adapter.js +68 -4
- package/src/client/conversation-control-adapter.js +2 -0
- package/src/client/cursor-adapter.js +19 -2
- package/src/client/custom-emblem.js +1 -1
- package/src/client/facility-geometry.js +14 -10
- package/src/client/facility-vector.js +1 -1
- package/src/client/index.js +4 -2
- package/src/client/particle-field-adapter.js +18 -9
- package/src/client/preferences.js +17 -2
- package/src/client/preview-markdown.js +18 -0
- package/src/client/session-summary-preview.js +479 -0
- package/src/client/sidebar-control-adapter.js +52 -6
- package/src/client/theme-settings-workbench.js +39 -2
- package/src/styles/facility-vector.css +91 -3
- package/src/styles/prts.css +27 -13
- package/src/styles/refinement.css +34 -15
- package/src/styles/theme-settings-workbench.css +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ash-qw/dsh-theme-prts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web and the official desktop client",
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@playwright/test": "1.55.1",
|
|
76
|
-
"jsdom": "24.1.3"
|
|
76
|
+
"jsdom": "24.1.3",
|
|
77
|
+
"markdown-it": "^14.3.2"
|
|
77
78
|
},
|
|
78
79
|
"dependencies": {
|
|
79
80
|
"sharp": "^0.35.5"
|
|
@@ -103,6 +103,52 @@ function cssUrl(value) {
|
|
|
103
103
|
|
|
104
104
|
export function createAssistantGlassAdapter({ document, window, avatarImage = '' }) {
|
|
105
105
|
let observer
|
|
106
|
+
let observationRoot
|
|
107
|
+
let groupAvatars = true
|
|
108
|
+
let avatarsDirty = true
|
|
109
|
+
const avatarSteps = new Set()
|
|
110
|
+
const emptySteps = new Set()
|
|
111
|
+
function refreshAvatars() {
|
|
112
|
+
if (!avatarsDirty || !observationRoot) return
|
|
113
|
+
avatarsDirty = false
|
|
114
|
+
for (const step of avatarSteps) {
|
|
115
|
+
if (!observationRoot.contains(step)) {
|
|
116
|
+
step.removeAttribute('data-prts-avatar-hidden')
|
|
117
|
+
avatarSteps.delete(step)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
emptySteps.clear()
|
|
121
|
+
if (!groupAvatars) {
|
|
122
|
+
for (const step of avatarSteps) step.removeAttribute('data-prts-avatar-hidden')
|
|
123
|
+
avatarSteps.clear()
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
const groups = new Map()
|
|
127
|
+
for (const node of observationRoot.querySelectorAll('[data-chat-flow-kind="user"], [data-chat-flow-kind="steering"], ' + STEP_SELECTOR)) {
|
|
128
|
+
const scope = node.closest('[data-conversation-scroll]') ?? observationRoot
|
|
129
|
+
let group = groups.get(scope)
|
|
130
|
+
if (!group) groups.set(scope, group = new Set())
|
|
131
|
+
if (node.getAttribute('data-chat-flow-kind') !== 'assistant-step') {
|
|
132
|
+
if (node.closest('[hidden], [data-turn-process-hidden="true"]')) continue
|
|
133
|
+
group.clear()
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const surface = stepSurfaces.get(node) ?? resolveAssistantSurface(node)
|
|
137
|
+
const hasContent = Boolean(surface?.textContent?.trim() || surface?.querySelector('img,video,audio,canvas,svg'))
|
|
138
|
+
if (!hasContent) emptySteps.add(node)
|
|
139
|
+
const eligible = hasContent && node.getAttribute('data-chat-group-part') !== 'reasoning'
|
|
140
|
+
&& !node.closest('[hidden], [data-turn-process-hidden="true"]')
|
|
141
|
+
const turn = node.getAttribute('data-chat-turn') ?? 'unassigned'
|
|
142
|
+
// The compact-answer flag disappears when expanded; process membership
|
|
143
|
+
// remains stable, so final responses keep their independent avatars.
|
|
144
|
+
const process = node.hasAttribute('data-turn-process-member')
|
|
145
|
+
const show = eligible && (!process || !group.has(turn))
|
|
146
|
+
if (show && process) group.add(turn)
|
|
147
|
+
if (show) node.removeAttribute('data-prts-avatar-hidden')
|
|
148
|
+
else if (!node.hasAttribute('data-prts-avatar-hidden')) node.setAttribute('data-prts-avatar-hidden', '')
|
|
149
|
+
avatarSteps.add(node)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
106
152
|
let scanFrame
|
|
107
153
|
const ownedSurfaces = new Set()
|
|
108
154
|
const ownedSteps = new Set()
|
|
@@ -198,6 +244,7 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
198
244
|
}
|
|
199
245
|
|
|
200
246
|
function scan() {
|
|
247
|
+
refreshAvatars()
|
|
201
248
|
const startedAt = window?.performance?.now?.() ?? Date.now()
|
|
202
249
|
let processed = 0
|
|
203
250
|
for (const step of pendingSteps) {
|
|
@@ -224,10 +271,16 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
224
271
|
|
|
225
272
|
function onMutations(mutations) {
|
|
226
273
|
for (const mutation of mutations) {
|
|
274
|
+
if (mutation.type === 'characterData') {
|
|
275
|
+
const step = mutation.target.parentElement?.closest(STEP_SELECTOR)
|
|
276
|
+
if (emptySteps.has(step)) avatarsDirty = true
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
227
279
|
const parentStep = mutation.target?.closest?.(STEP_SELECTOR)
|
|
228
280
|
const stableContentMutation = parentStep
|
|
229
281
|
&& stableBodyContains(parentStep, mutation.target)
|
|
230
282
|
&& !(mutation.type === 'attributes' && stepSurfaces.get(parentStep) === mutation.target)
|
|
283
|
+
if (!stableContentMutation || emptySteps.has(parentStep)) avatarsDirty = true
|
|
231
284
|
if (parentStep && !stableContentMutation) {
|
|
232
285
|
pendingSteps.add(parentStep)
|
|
233
286
|
}
|
|
@@ -239,7 +292,7 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
239
292
|
if (surface && (surface === node || node.contains?.(surface))) pendingSteps.add(parentStep)
|
|
240
293
|
}
|
|
241
294
|
}
|
|
242
|
-
if (pendingSteps.size) queueScan()
|
|
295
|
+
if (pendingSteps.size || avatarsDirty) queueScan()
|
|
243
296
|
}
|
|
244
297
|
|
|
245
298
|
function removeMarkers() {
|
|
@@ -248,13 +301,19 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
248
301
|
}
|
|
249
302
|
|
|
250
303
|
return {
|
|
251
|
-
start() {
|
|
304
|
+
start({ groupAvatars: enabled = true } = {}) {
|
|
252
305
|
if (!document) return
|
|
306
|
+
if (groupAvatars !== enabled) {
|
|
307
|
+
groupAvatars = enabled
|
|
308
|
+
avatarsDirty = true
|
|
309
|
+
refreshAvatars()
|
|
310
|
+
}
|
|
253
311
|
mountAvatarImage()
|
|
254
|
-
|
|
312
|
+
observationRoot = document.querySelector('[data-prts-region="operation"]')
|
|
255
313
|
?? document.querySelector('[data-slot="main.conversation"], [data-slot="conversation"]')
|
|
256
314
|
if (!observationRoot) return
|
|
257
315
|
if (observer) return
|
|
316
|
+
avatarsDirty = true
|
|
258
317
|
collectInitialSteps(observationRoot)
|
|
259
318
|
scan()
|
|
260
319
|
const Observer = window?.MutationObserver
|
|
@@ -263,8 +322,9 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
263
322
|
observer.observe(observationRoot, {
|
|
264
323
|
subtree: true,
|
|
265
324
|
childList: true,
|
|
325
|
+
characterData: true,
|
|
266
326
|
attributes: true,
|
|
267
|
-
attributeFilter: ['class', 'data-chat-flow-kind', 'data-chat-group-part', 'data-markdown', 'data-slot'],
|
|
327
|
+
attributeFilter: ['class', 'data-chat-flow-kind', 'data-chat-group-part', 'data-markdown', 'data-slot', 'data-chat-turn', 'hidden', 'data-turn-process-hidden', 'data-turn-process-member', 'data-turn-process-answer'],
|
|
268
328
|
})
|
|
269
329
|
},
|
|
270
330
|
dispose() {
|
|
@@ -276,6 +336,10 @@ export function createAssistantGlassAdapter({ document, window, avatarImage = ''
|
|
|
276
336
|
}
|
|
277
337
|
scanFrame = undefined
|
|
278
338
|
removeMarkers()
|
|
339
|
+
for (const step of avatarSteps) step.removeAttribute('data-prts-avatar-hidden')
|
|
340
|
+
avatarSteps.clear()
|
|
341
|
+
emptySteps.clear()
|
|
342
|
+
observationRoot = undefined
|
|
279
343
|
releaseAvatarImage()
|
|
280
344
|
},
|
|
281
345
|
}
|
|
@@ -14,6 +14,8 @@ function closestWithClassSuffix(node, suffix) {
|
|
|
14
14
|
function classify(button, operation) {
|
|
15
15
|
// The host's process disclosure owns its transparent, full-width hit area.
|
|
16
16
|
if (button.hasAttribute('data-turn-process')) return null
|
|
17
|
+
// Changed-file cards own one outer surface; their header and rows stay native.
|
|
18
|
+
if (button.closest('[data-changed-files]')) return null
|
|
17
19
|
// File previews are transparent hit targets; the enclosing card owns its material and outline.
|
|
18
20
|
if (button.closest('[data-presented-file]') && hasClassSuffix(button, '_cardPreview')) return null
|
|
19
21
|
if (button.tagName !== 'BUTTON' || !operation.contains(button)) return null
|
|
@@ -88,8 +88,18 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
88
88
|
let originalCursor
|
|
89
89
|
let originalPriority
|
|
90
90
|
let appliedCursor
|
|
91
|
+
let appliedPriority
|
|
92
|
+
function ownsHandCursor() {
|
|
93
|
+
return handTarget?.style.cursor === appliedCursor
|
|
94
|
+
&& handTarget.style.getPropertyPriority('cursor') === appliedPriority
|
|
95
|
+
}
|
|
96
|
+
function stopIfHandChanged() {
|
|
97
|
+
if (!handTarget || ownsHandCursor()) return false
|
|
98
|
+
reset()
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
91
101
|
function restoreHandTarget() {
|
|
92
|
-
if (handTarget &&
|
|
102
|
+
if (handTarget && ownsHandCursor()) {
|
|
93
103
|
if (originalCursor) handTarget.style.setProperty('cursor', originalCursor, originalPriority)
|
|
94
104
|
else handTarget.style.removeProperty('cursor')
|
|
95
105
|
}
|
|
@@ -103,6 +113,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
103
113
|
return target && target.getElementsByTagName('*').length <= maxAnimatedDescendants
|
|
104
114
|
}
|
|
105
115
|
function followHand(target) {
|
|
116
|
+
if (stopIfHandChanged()) return
|
|
106
117
|
if (target === handTarget && target !== textTarget) return
|
|
107
118
|
restoreHandTarget()
|
|
108
119
|
if (!canAnimate(target) || !handCursor || target === textTarget) return
|
|
@@ -111,13 +122,16 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
111
122
|
handTarget = target
|
|
112
123
|
originalCursor = target.style.getPropertyValue('cursor')
|
|
113
124
|
originalPriority = target.style.getPropertyPriority('cursor')
|
|
125
|
+
appliedCursor = originalCursor
|
|
126
|
+
appliedPriority = originalPriority
|
|
114
127
|
target.setAttribute('data-prts-hand-active', '')
|
|
115
128
|
applyHandFrame()
|
|
116
129
|
}
|
|
117
130
|
function applyHandFrame() {
|
|
118
|
-
if (!handTarget) return
|
|
131
|
+
if (stopIfHandChanged() || !handTarget) return
|
|
119
132
|
handTarget.style.setProperty('cursor', handCursor)
|
|
120
133
|
appliedCursor = handTarget.style.cursor
|
|
134
|
+
appliedPriority = handTarget.style.getPropertyPriority('cursor')
|
|
121
135
|
}
|
|
122
136
|
function clearTimer() {
|
|
123
137
|
if (timer !== undefined) window.clearTimeout(timer)
|
|
@@ -144,10 +158,12 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
144
158
|
let index = 0
|
|
145
159
|
function next() {
|
|
146
160
|
timer = undefined
|
|
161
|
+
if (stopIfHandChanged()) return
|
|
147
162
|
// The source ANI ends with a 60-second hold frame. Use the static state instead.
|
|
148
163
|
if (index >= frames.length - 1) { done(); return }
|
|
149
164
|
const frame = frames[index++]
|
|
150
165
|
show(frame.url)
|
|
166
|
+
if (!handCursor) return
|
|
151
167
|
timer = window.setTimeout(next, frame.duration)
|
|
152
168
|
}
|
|
153
169
|
next()
|
|
@@ -179,6 +195,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
179
195
|
play(gestureAssets.release, reset)
|
|
180
196
|
}
|
|
181
197
|
function up(event) {
|
|
198
|
+
if (stopIfHandChanged()) return
|
|
182
199
|
if (pointerId === undefined || event.pointerId !== pointerId || event.button !== 0) return
|
|
183
200
|
pointerId = undefined
|
|
184
201
|
if (reduced()) { reset(); return }
|
|
@@ -245,7 +245,7 @@ export function customEmblemMarkup() {
|
|
|
245
245
|
return `<section data-prts-custom-emblem>
|
|
246
246
|
<header><strong>徽记管理</strong><small>新会话与正文分别设置,可在任意会话中让 AI 调整,设置在当前 DSH 实例共享</small></header>
|
|
247
247
|
<div data-prts-emblem-hero-panel><h4>新会话徽记</h4><div data-prts-emblem-hero-row></div></div>
|
|
248
|
-
<div><h4>正文轮换徽记</h4><small>按列表顺序随正文滚动轮换,可混合使用内置与上传的图片,最多 12 张。长按徽记选项任意位置拖动排序,松开保存。</small>
|
|
248
|
+
<div><h4>正文轮换徽记</h4><small data-prts-emblem-baker-note hidden>Baker 会话正文不显示或运行粒子徽记;下列配置保留供经典主题使用,新会话徽记不受影响。</small><small>按列表顺序随正文滚动轮换,可混合使用内置与上传的图片,最多 12 张。长按徽记选项任意位置拖动排序,松开保存。</small>
|
|
249
249
|
<ol data-prts-emblem-list></ol>
|
|
250
250
|
<div data-prts-emblem-actions><button type="button" data-prts-emblem-add>新增徽记</button><button type="button" data-prts-emblem-reset-list>恢复内置列表</button></div>
|
|
251
251
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const FACILITY_GEOMETRY = Object.freeze({
|
|
2
2
|
stroke: 1,
|
|
3
3
|
corner: 1,
|
|
4
|
-
sideDepth:
|
|
4
|
+
sideDepth: 14 / 17,
|
|
5
5
|
sideOpening: 10,
|
|
6
6
|
sideInner: 6,
|
|
7
7
|
topOpening: 50,
|
|
@@ -20,7 +20,7 @@ function point(x, y) {
|
|
|
20
20
|
return `${number(x)} ${number(y)}`
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function createFacilityPath({ width, height, topNotch = false, sessionNotch = false } = {}) {
|
|
23
|
+
export function createFacilityPath({ width, height, topNotch = false, sessionNotch = false, spine = false } = {}) {
|
|
24
24
|
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 2 || height <= FACILITY_GEOMETRY.sideOpening + 2) return ''
|
|
25
25
|
|
|
26
26
|
const halfStroke = FACILITY_GEOMETRY.stroke / 2
|
|
@@ -28,6 +28,10 @@ export function createFacilityPath({ width, height, topNotch = false, sessionNot
|
|
|
28
28
|
const right = width - halfStroke
|
|
29
29
|
const top = halfStroke
|
|
30
30
|
const bottom = height - halfStroke
|
|
31
|
+
// Keep the original RIIC sidebar width ratio; align notch ends with the card face.
|
|
32
|
+
const sideDepth = spine ? (width - FACILITY_GEOMETRY.stroke) * 2 / 17 : FACILITY_GEOMETRY.sideDepth
|
|
33
|
+
const sideOpening = FACILITY_GEOMETRY.sideOpening
|
|
34
|
+
const sideInner = FACILITY_GEOMETRY.sideInner
|
|
31
35
|
const centerX = width / 2
|
|
32
36
|
const centerY = height / 2
|
|
33
37
|
const points = [point(left + FACILITY_GEOMETRY.corner, top)]
|
|
@@ -47,18 +51,18 @@ export function createFacilityPath({ width, height, topNotch = false, sessionNot
|
|
|
47
51
|
points.push(
|
|
48
52
|
point(right - FACILITY_GEOMETRY.corner, top),
|
|
49
53
|
point(right, top + FACILITY_GEOMETRY.corner),
|
|
50
|
-
point(right, centerY -
|
|
51
|
-
point(right -
|
|
52
|
-
point(right -
|
|
53
|
-
point(right, centerY +
|
|
54
|
+
point(right, centerY - sideOpening / 2),
|
|
55
|
+
point(right - sideDepth, centerY - sideInner / 2),
|
|
56
|
+
point(right - sideDepth, centerY + sideInner / 2),
|
|
57
|
+
point(right, centerY + sideOpening / 2),
|
|
54
58
|
point(right, bottom - FACILITY_GEOMETRY.corner),
|
|
55
59
|
point(right - FACILITY_GEOMETRY.corner, bottom),
|
|
56
60
|
point(left + FACILITY_GEOMETRY.corner, bottom),
|
|
57
61
|
point(left, bottom - FACILITY_GEOMETRY.corner),
|
|
58
|
-
point(left, centerY +
|
|
59
|
-
point(left +
|
|
60
|
-
point(left +
|
|
61
|
-
point(left, centerY -
|
|
62
|
+
point(left, centerY + sideOpening / 2),
|
|
63
|
+
point(left + sideDepth, centerY + sideInner / 2),
|
|
64
|
+
point(left + sideDepth, centerY - sideInner / 2),
|
|
65
|
+
point(left, centerY - sideOpening / 2),
|
|
62
66
|
point(left, top + FACILITY_GEOMETRY.corner),
|
|
63
67
|
)
|
|
64
68
|
|
|
@@ -279,7 +279,7 @@ export function renderFacilityGraphic(owner, { width, height } = {}) {
|
|
|
279
279
|
renderStates.set(graphic, state)
|
|
280
280
|
}
|
|
281
281
|
const { statusUnderlay, idleIndicator, silhouette, pickup, lifeline } = state
|
|
282
|
-
const path = createFacilityPath({ width, height, topNotch, sessionNotch })
|
|
282
|
+
const path = createFacilityPath({ width, height, topNotch, sessionNotch, spine: graphic.getAttribute('data-prts-facility-svg') === 'spine' })
|
|
283
283
|
graphic.setAttribute('viewBox', `0 0 ${Number(width.toFixed(3))} ${Number(height.toFixed(3))}`)
|
|
284
284
|
for (const layer of state.layers) layer.setAttribute('d', path)
|
|
285
285
|
statusUnderlay?.setAttribute('d', createFacilityPath({ width, height }))
|
package/src/client/index.js
CHANGED
|
@@ -100,6 +100,8 @@ export function applyPrtsPlugin(ctx, environment) {
|
|
|
100
100
|
workspaces: contextService(ctx, 'workspaces'),
|
|
101
101
|
getSessionStatus: () => contextService(ctx, 'uiSession')?.sessionStatus,
|
|
102
102
|
getUiWorkspace: () => contextService(ctx, 'uiWorkspace'),
|
|
103
|
+
getSessions: () => contextService(ctx, 'sessions'),
|
|
104
|
+
getUiConversation: () => contextService(ctx, 'uiConversation'),
|
|
103
105
|
})
|
|
104
106
|
const floatingGlass = createFloatingGlassAdapter({ document, window })
|
|
105
107
|
const adapter = createRc7Adapter({ document })
|
|
@@ -252,12 +254,12 @@ export function applyPrtsPlugin(ctx, environment) {
|
|
|
252
254
|
}
|
|
253
255
|
particleField.update(preferences)
|
|
254
256
|
composerGlass.start()
|
|
255
|
-
if (preferences.conversationStyle === 'deck-chat') assistantGlass.start()
|
|
257
|
+
if (preferences.conversationStyle === 'deck-chat') assistantGlass.start({ groupAvatars: preferences.bakerGroupAvatars })
|
|
256
258
|
else assistantGlass.dispose()
|
|
257
259
|
toBottom.start()
|
|
258
260
|
conversationControls.start()
|
|
259
261
|
layoutDiagnostics.start()
|
|
260
|
-
sidebarControls.start()
|
|
262
|
+
sidebarControls.start(preferences)
|
|
261
263
|
floatingGlass.start()
|
|
262
264
|
}
|
|
263
265
|
|
|
@@ -431,7 +431,8 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
431
431
|
: preferences.conversationParticleDensity
|
|
432
432
|
const densityProfile = density => PARTICLE_DENSITY_PROFILES[density] ?? PARTICLE_DENSITY_PROFILES.standard
|
|
433
433
|
const particleRadius = density => densityProfile(density ?? activeDensity()).radius
|
|
434
|
-
const
|
|
434
|
+
const fieldEnabled = () => heroActive || (preferences.conversationStyle !== 'deck-chat' && preferences.conversationEmblem !== false)
|
|
435
|
+
const canRenderFrame = () => fieldEnabled() && !disposed && documentVisible && canvasVisible && !hydrationPaused && !startupPaused && !schemeTransitionPaused && !resizing
|
|
435
436
|
const layoutWidth = () => width
|
|
436
437
|
const layoutHeight = () => height
|
|
437
438
|
const layoutShiftX = () => 0
|
|
@@ -496,7 +497,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
496
497
|
}
|
|
497
498
|
|
|
498
499
|
function populationSource(density = activeDensity()) {
|
|
499
|
-
return heroActive ? targetsForHero(density) : targetsForSegment(segment, density)
|
|
500
|
+
return !fieldEnabled() ? [] : heroActive ? targetsForHero(density) : targetsForSegment(segment, density)
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
function targetParticleCount(density = activeDensity()) {
|
|
@@ -697,7 +698,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
697
698
|
|
|
698
699
|
function scheduleTargetPrewarm() {
|
|
699
700
|
cancelTargetPrewarm()
|
|
700
|
-
if (typeof window.requestIdleCallback !== 'function' || !particles.length) return
|
|
701
|
+
if (preferences.conversationEmblem === false || preferences.conversationStyle === 'deck-chat' || !fieldEnabled() || typeof window.requestIdleCallback !== 'function' || !particles.length) return
|
|
701
702
|
targetPrewarmQueue = (customEmblemConfiguration?.conversation ?? emblemSequence).map((_, index) => index)
|
|
702
703
|
.filter(index => index !== sequenceIndexForSegment(segment))
|
|
703
704
|
const warmNext = () => {
|
|
@@ -718,6 +719,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
718
719
|
}
|
|
719
720
|
|
|
720
721
|
function buildParticles() {
|
|
722
|
+
if (!fieldEnabled()) { particles = []; cancelTargetPrewarm(); return false }
|
|
721
723
|
if (startupPaused) return false
|
|
722
724
|
const selected = populationSource()
|
|
723
725
|
if (!selected.length) {
|
|
@@ -796,6 +798,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
796
798
|
}
|
|
797
799
|
|
|
798
800
|
function startDensityReassembly(nextCount, force = false) {
|
|
801
|
+
if (!fieldEnabled()) return
|
|
799
802
|
const baseNextTargets = populationSource()
|
|
800
803
|
const targetCount = baseNextTargets.length
|
|
801
804
|
if (!targetCount) {
|
|
@@ -921,6 +924,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
921
924
|
}
|
|
922
925
|
|
|
923
926
|
function commitTransitionTargets(targetSegment, snap = false, density = preferences.conversationParticleDensity) {
|
|
927
|
+
if (!fieldEnabled()) return
|
|
924
928
|
const selected = targetsForSegment(targetSegment, density)
|
|
925
929
|
if (!selected.length) return
|
|
926
930
|
segment = targetSegment
|
|
@@ -1507,7 +1511,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1507
1511
|
heroActive = true
|
|
1508
1512
|
scroller?.setAttribute('data-prts-hero-active', '')
|
|
1509
1513
|
resetTraversal()
|
|
1510
|
-
|
|
1514
|
+
updateFrameGate()
|
|
1511
1515
|
}
|
|
1512
1516
|
|
|
1513
1517
|
function leaveHero() {
|
|
@@ -1516,6 +1520,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1516
1520
|
heroMarker = undefined
|
|
1517
1521
|
heroActive = false
|
|
1518
1522
|
scroller?.removeAttribute('data-prts-hero-active')
|
|
1523
|
+
if (!fieldEnabled()) { resetTraversal(); updateFrameGate(); return }
|
|
1519
1524
|
side = -1
|
|
1520
1525
|
segment = 0
|
|
1521
1526
|
traversalOffset = -currentScrollTop() * particleTraversalSpeed() / traversalViewport()
|
|
@@ -1558,7 +1563,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1558
1563
|
if (heroActive) leaveHero()
|
|
1559
1564
|
}
|
|
1560
1565
|
function onScroll() {
|
|
1561
|
-
if (!scroller || phoneStatic()) return
|
|
1566
|
+
if (!fieldEnabled() || !scroller || phoneStatic()) return
|
|
1562
1567
|
if (heroActive) {
|
|
1563
1568
|
scheduleFrame()
|
|
1564
1569
|
return
|
|
@@ -1570,7 +1575,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1570
1575
|
}
|
|
1571
1576
|
|
|
1572
1577
|
function onPointerMove(event) {
|
|
1573
|
-
if (!operation || transition || motionReduced() || phoneStatic()) return
|
|
1578
|
+
if (!fieldEnabled() || !operation || transition || motionReduced() || phoneStatic()) return
|
|
1574
1579
|
pointerClientX = event.clientX
|
|
1575
1580
|
pointerClientY = event.clientY
|
|
1576
1581
|
pointerActive = true
|
|
@@ -1607,7 +1612,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1607
1612
|
}
|
|
1608
1613
|
|
|
1609
1614
|
function registerContentMutations(mutations) {
|
|
1610
|
-
if (phoneStatic()) return
|
|
1615
|
+
if (!fieldEnabled() || phoneStatic()) return
|
|
1611
1616
|
let weight = mutations.length
|
|
1612
1617
|
for (const mutation of mutations) {
|
|
1613
1618
|
for (const node of [...mutation.addedNodes, ...mutation.removedNodes]) {
|
|
@@ -1652,6 +1657,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1652
1657
|
}
|
|
1653
1658
|
|
|
1654
1659
|
function updateFrameGate() {
|
|
1660
|
+
if (canvas) canvas.hidden = !fieldEnabled()
|
|
1655
1661
|
const now = Number(window.performance?.now?.()) || 0
|
|
1656
1662
|
if (!canRenderFrame()) {
|
|
1657
1663
|
pauseStartedAt ??= now
|
|
@@ -1741,7 +1747,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1741
1747
|
if (!phoneStatic()) restoreTraversalFromScroll()
|
|
1742
1748
|
commitTransitionTargets(segment, true)
|
|
1743
1749
|
}
|
|
1744
|
-
|
|
1750
|
+
updateFrameGate()
|
|
1745
1751
|
}
|
|
1746
1752
|
|
|
1747
1753
|
function mount(nextOperation, nextScroller) {
|
|
@@ -1768,6 +1774,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1768
1774
|
targetCache.clear()
|
|
1769
1775
|
initializeFieldSize()
|
|
1770
1776
|
syncHeroState()
|
|
1777
|
+
canvas.hidden = !fieldEnabled()
|
|
1771
1778
|
if (!heroActive) restoreTraversalFromScroll()
|
|
1772
1779
|
if (!particles.length && maskRecords.length) buildParticles()
|
|
1773
1780
|
if (context && particles.length) {
|
|
@@ -1880,6 +1887,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1880
1887
|
}
|
|
1881
1888
|
|
|
1882
1889
|
findMount()
|
|
1890
|
+
if (!fieldEnabled()) resetTraversal()
|
|
1883
1891
|
const previousActiveDensity = heroActive
|
|
1884
1892
|
? previousPreferences.heroParticleDensity
|
|
1885
1893
|
: previousPreferences.conversationParticleDensity
|
|
@@ -1914,10 +1922,11 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1914
1922
|
}
|
|
1915
1923
|
}
|
|
1916
1924
|
if (ambient) ambient.hidden = preferences.texture === 'off' && preferences.conversationStyle !== 'deck-chat'
|
|
1917
|
-
|
|
1925
|
+
updateFrameGate()
|
|
1918
1926
|
},
|
|
1919
1927
|
inspect() {
|
|
1920
1928
|
return {
|
|
1929
|
+
enabled: fieldEnabled(),
|
|
1921
1930
|
mounted: Boolean(ambient?.isConnected && canvas?.isConnected),
|
|
1922
1931
|
hero: heroActive,
|
|
1923
1932
|
side,
|
|
@@ -30,6 +30,12 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
30
30
|
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
31
31
|
particlePattern: 'orthogonal',
|
|
32
32
|
railDefaultHidden: false,
|
|
33
|
+
sessionPreview: true,
|
|
34
|
+
previewOngoing: true,
|
|
35
|
+
previewWarning: true,
|
|
36
|
+
previewDone: true,
|
|
37
|
+
previewDoubleClick: true,
|
|
38
|
+
|
|
33
39
|
sessionFlow: true,
|
|
34
40
|
sessionGlow: true,
|
|
35
41
|
sessionFlowPalette: 'triad',
|
|
@@ -38,6 +44,8 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
38
44
|
sessionFlowColorFront: SESSION_FLOW_PALETTES.triad.front,
|
|
39
45
|
sessionFlowSpeed: SESSION_FLOW_SPEED_DEFAULT,
|
|
40
46
|
conversationStyle: 'native',
|
|
47
|
+
bakerGroupAvatars: true,
|
|
48
|
+
conversationEmblem: true,
|
|
41
49
|
})
|
|
42
50
|
|
|
43
51
|
export const VISUAL_PRESETS = Object.freeze({
|
|
@@ -78,8 +86,8 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
78
86
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
79
87
|
material: Object.freeze(['glass']),
|
|
80
88
|
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
81
|
-
navigation: Object.freeze(['railDefaultHidden']),
|
|
82
|
-
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle']),
|
|
89
|
+
navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewWarning', 'previewDone', 'previewDoubleClick']),
|
|
90
|
+
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'conversationEmblem']),
|
|
83
91
|
})
|
|
84
92
|
|
|
85
93
|
const TEXTURES = new Set(['off', 'restrained', 'full'])
|
|
@@ -195,6 +203,13 @@ export function normalizePreferences(value) {
|
|
|
195
203
|
sessionFlowColorCore: sessionFlowColors.core,
|
|
196
204
|
sessionFlowColorFront: sessionFlowColors.front,
|
|
197
205
|
sessionFlowSpeed: sessionFlowSpeedOr(input.sessionFlowSpeed, DEFAULT_PREFERENCES.sessionFlowSpeed),
|
|
206
|
+
sessionPreview: booleanOr(input.sessionPreview, DEFAULT_PREFERENCES.sessionPreview),
|
|
207
|
+
previewOngoing: booleanOr(input.previewOngoing, DEFAULT_PREFERENCES.previewOngoing),
|
|
208
|
+
previewWarning: booleanOr(input.previewWarning, DEFAULT_PREFERENCES.previewWarning),
|
|
209
|
+
previewDone: booleanOr(input.previewDone, DEFAULT_PREFERENCES.previewDone),
|
|
210
|
+
previewDoubleClick: booleanOr(input.previewDoubleClick, DEFAULT_PREFERENCES.previewDoubleClick),
|
|
211
|
+
conversationEmblem: booleanOr(input.conversationEmblem, DEFAULT_PREFERENCES.conversationEmblem),
|
|
212
|
+
bakerGroupAvatars: booleanOr(input.bakerGroupAvatars, DEFAULT_PREFERENCES.bakerGroupAvatars),
|
|
198
213
|
conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
|
|
199
214
|
}
|
|
200
215
|
normalized.preset = matchedVisualPreset(normalized)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it'
|
|
2
|
+
|
|
3
|
+
let previewMarkdownParser
|
|
4
|
+
export function renderPreviewMarkdown(text) {
|
|
5
|
+
if (!previewMarkdownParser) {
|
|
6
|
+
previewMarkdownParser = new MarkdownIt({html:false,linkify:false,typographer:false})
|
|
7
|
+
// Previews never fetch remote images or render model-supplied HTML.
|
|
8
|
+
previewMarkdownParser.renderer.rules.image = (tokens,index) =>
|
|
9
|
+
previewMarkdownParser.utils.escapeHtml(tokens[index].content || '图片')
|
|
10
|
+
const link = previewMarkdownParser.renderer.rules.link_open
|
|
11
|
+
previewMarkdownParser.renderer.rules.link_open = (tokens,index,options,env,self) => {
|
|
12
|
+
tokens[index].attrSet('target','_blank')
|
|
13
|
+
tokens[index].attrSet('rel','noopener noreferrer')
|
|
14
|
+
return link ? link(tokens,index,options,env,self) : self.renderToken(tokens,index,options)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return previewMarkdownParser.render(text)
|
|
18
|
+
}
|