@ash-qw/dsh-theme-prts 2.1.0 → 2.2.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 +27 -4
- package/lib/client.js +272 -46
- package/package.json +1 -1
- package/src/client/custom-background.js +3 -0
- package/src/client/host-geometry-adapter.js +13 -0
- package/src/client/opacity-dial.js +4 -4
- package/src/client/operations-shell-v2.js +37 -8
- package/src/client/preferences.js +6 -1
- package/src/client/session-summary-preview.js +148 -16
- package/src/client/sidebar-control-adapter.js +1 -1
- package/src/client/theme-controller-v2.js +38 -13
- package/src/client/theme-settings-workbench.js +30 -1
- package/src/index.js +21 -4
- package/src/styles/custom-background.css +156 -151
- package/src/styles/facility-vector.css +26 -4
- package/src/styles/prts.css +22 -16
- package/src/styles/theme-settings-workbench.css +10 -0
package/package.json
CHANGED
|
@@ -18,6 +18,8 @@ export function createBackgroundStore({ window, document }) {
|
|
|
18
18
|
if (url) root.style.setProperty(`--prts-background-${slot}`, `url("${url}")`)
|
|
19
19
|
else root.style.removeProperty(`--prts-background-${slot}`)
|
|
20
20
|
}
|
|
21
|
+
const ratio = Number(state.sidebar?.width) / Number(state.sidebar?.height)
|
|
22
|
+
document.documentElement.style.setProperty('--prts-sidebar-image-ratio', String(Number.isFinite(ratio) && ratio > 0 ? ratio : 1))
|
|
21
23
|
for (const listener of listeners) listener(state)
|
|
22
24
|
}
|
|
23
25
|
const request = async data => {
|
|
@@ -52,6 +54,7 @@ export function createBackgroundStore({ window, document }) {
|
|
|
52
54
|
catch (error) { await refresh().catch(() => {}); throw error }
|
|
53
55
|
},
|
|
54
56
|
dispose() {
|
|
57
|
+
document.documentElement.style.removeProperty('--prts-sidebar-image-ratio')
|
|
55
58
|
disposed = true; abort?.abort(); events?.close(); listeners.clear(); window.removeEventListener('focus', focus)
|
|
56
59
|
for (const slot of Object.keys(backgroundLabels)) {
|
|
57
60
|
document.documentElement.removeAttribute(`data-prts-background-${slot}`)
|
|
@@ -16,6 +16,8 @@ function cssPixels(value) {
|
|
|
16
16
|
export function createHostGeometryAdapter({ document, window }) {
|
|
17
17
|
const root = document?.documentElement
|
|
18
18
|
let frame
|
|
19
|
+
let sidebar
|
|
20
|
+
let previousSidebarWidth
|
|
19
21
|
let resizeObserver
|
|
20
22
|
let frameRequest
|
|
21
23
|
let resizeSettleTimer
|
|
@@ -31,6 +33,10 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
31
33
|
if (root.style.getPropertyValue(TOP_INSET_PROPERTY) !== value) {
|
|
32
34
|
root.style.setProperty(TOP_INSET_PROPERTY, value)
|
|
33
35
|
}
|
|
36
|
+
if (sidebar?.isConnected) {
|
|
37
|
+
const width = cssPixels(sidebar.getBoundingClientRect().width)
|
|
38
|
+
if (root.style.getPropertyValue('--prts-sidebar-width') !== width) root.style.setProperty('--prts-sidebar-width', width)
|
|
39
|
+
}
|
|
34
40
|
return top
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -62,6 +68,7 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
62
68
|
function onGeometryResize(entries = []) {
|
|
63
69
|
let shouldMeasure = false
|
|
64
70
|
for (const entry of entries) {
|
|
71
|
+
if (entry.target === sidebar) { shouldMeasure = true; continue }
|
|
65
72
|
if (entry.target !== frame) continue
|
|
66
73
|
const height = Number(entry.contentRect?.height)
|
|
67
74
|
if (!Number.isFinite(height) || height <= 0) {
|
|
@@ -91,6 +98,9 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
91
98
|
else root.style.removeProperty(TOP_INSET_PROPERTY)
|
|
92
99
|
previousValue = undefined
|
|
93
100
|
previousPriority = undefined
|
|
101
|
+
if (previousSidebarWidth) root.style.setProperty('--prts-sidebar-width', previousSidebarWidth)
|
|
102
|
+
else root.style.removeProperty('--prts-sidebar-width')
|
|
103
|
+
sidebar = undefined
|
|
94
104
|
frame = undefined
|
|
95
105
|
observedFrameHeight = undefined
|
|
96
106
|
started = false
|
|
@@ -105,6 +115,8 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
105
115
|
}
|
|
106
116
|
if (started) dispose()
|
|
107
117
|
frame = nextFrame
|
|
118
|
+
sidebar = frame.querySelector?.('[data-prts-region="sessions"]')
|
|
119
|
+
previousSidebarWidth = root.style.getPropertyValue('--prts-sidebar-width')
|
|
108
120
|
previousValue = root.style.getPropertyValue(TOP_INSET_PROPERTY)
|
|
109
121
|
previousPriority = root.style.getPropertyPriority(TOP_INSET_PROPERTY)
|
|
110
122
|
started = true
|
|
@@ -112,6 +124,7 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
112
124
|
if (typeof window?.ResizeObserver === 'function') {
|
|
113
125
|
resizeObserver = new window.ResizeObserver(onGeometryResize)
|
|
114
126
|
resizeObserver.observe(frame)
|
|
127
|
+
if (sidebar) resizeObserver.observe(sidebar)
|
|
115
128
|
}
|
|
116
129
|
window?.addEventListener?.('resize', onWindowResize, { passive: true })
|
|
117
130
|
scheduleSettledMeasure()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// See src/vendor/react-bits/LICENSE.md and THIRD_PARTY_NOTICES.md.
|
|
3
3
|
export const BACKGROUND_OPACITY_CONTROLS = Object.freeze([
|
|
4
4
|
{ key: 'sidebarOpacity', label: '会话列表侧栏', property: '--prts-sidebar-opacity', value: 94, blurKey: 'sidebarBlur', blurProperty: '--prts-sidebar-blur', blurDefault: 8 },
|
|
5
|
-
{ key: 'railOpacity', label: '设置侧栏', property: '--prts-rail-opacity', value: 70, blurKey: 'railBlur', blurProperty: '--prts-rail-blur', blurDefault: 12
|
|
5
|
+
{ key: 'railOpacity', label: '设置侧栏', property: '--prts-rail-opacity', value: 70, blurKey: 'railBlur', blurProperty: '--prts-rail-blur', blurDefault: 12 },
|
|
6
6
|
{ key: 'bakerOpacity', label: 'Baker 气泡', property: '--prts-baker-opacity', value: 50, blurKey: 'bakerBlur', blurProperty: '--prts-baker-blur', blurDefault: 8, theme: 'deck-chat' },
|
|
7
7
|
{ key: 'classicUserOpacity', label: '经典用户消息', property: '--prts-classic-user-opacity', value: 50, blurKey: 'classicUserBlur', blurProperty: '--prts-classic-user-blur', blurDefault: 8, theme: 'native' },
|
|
8
8
|
])
|
|
@@ -109,8 +109,7 @@ export function mountOpacityDials({ section, window, onChange }) {
|
|
|
109
109
|
paint(value)
|
|
110
110
|
return {
|
|
111
111
|
update(preferences, active) {
|
|
112
|
-
container.hidden = Boolean(
|
|
113
|
-
|| (definition.fullBackgroundOnly && preferences.backgroundMode !== 'full'))
|
|
112
|
+
container.hidden = Boolean(definition.theme && preferences.conversationStyle !== definition.theme)
|
|
114
113
|
saved = 100 - Math.round((preferences[definition.key] ?? (100 - definition.value) / 100) * 100)
|
|
115
114
|
if (!grip && section.ownerDocument.activeElement !== input) paint(saved)
|
|
116
115
|
savedBlur = preferences[definition.blurKey] ?? definition.blurDefault
|
|
@@ -127,7 +126,8 @@ export function mountOpacityDials({ section, window, onChange }) {
|
|
|
127
126
|
const split = preferences.backgroundMode === 'split'
|
|
128
127
|
section.querySelector('[data-prts-opacity-settings]').hidden = preferences.backgroundMode === 'off'
|
|
129
128
|
const conversationImage = full || (split && Boolean(state.conversation))
|
|
130
|
-
const
|
|
129
|
+
const sidebarImage = full || (split && Boolean(state.sidebar))
|
|
130
|
+
const active = [sidebarImage, sidebarImage,
|
|
131
131
|
conversationImage && preferences.conversationStyle === 'deck-chat',
|
|
132
132
|
conversationImage && preferences.conversationStyle === 'native']
|
|
133
133
|
controls.forEach((control,i)=>control.update(preferences,active[i]))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SCHEME_COMMIT_PROGRESS } from './theme-controller-v2.js'
|
|
1
2
|
import { createThemeSettingsOverlay, themeSettingsMarkup } from './theme-settings-workbench.js'
|
|
2
3
|
|
|
3
4
|
const PHONE_RAIL_BREAKPOINT = 640
|
|
@@ -83,9 +84,9 @@ export function createOperationsShell({
|
|
|
83
84
|
try { return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true } catch { return false }
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
function phoneRailGesture() {
|
|
87
|
+
function phoneRailGesture(pointerType) {
|
|
87
88
|
if (railMode !== 'overlay' || Number(window.innerWidth) > PHONE_RAIL_BREAKPOINT) return false
|
|
88
|
-
return railPointerQuery?.matches === true
|
|
89
|
+
return pointerType === 'touch' || railPointerQuery?.matches === true
|
|
89
90
|
|| railHoverQuery?.matches === true
|
|
90
91
|
|| Number(window.navigator?.maxTouchPoints) > 0
|
|
91
92
|
}
|
|
@@ -105,13 +106,16 @@ export function createOperationsShell({
|
|
|
105
106
|
|
|
106
107
|
function armRailGesture() {
|
|
107
108
|
railHoldTimer = undefined
|
|
108
|
-
if (!railPointer || !phoneRailGesture()) return
|
|
109
|
+
if (!railPointer || !phoneRailGesture('touch')) return
|
|
109
110
|
railPointer.armed = true
|
|
110
111
|
railLauncher?.setAttribute('data-prts-rail-gesture-active', '')
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
let railInputType
|
|
113
115
|
function onRailPointerDown(event) {
|
|
114
|
-
|
|
116
|
+
railInputType = event.pointerType || 'mouse'
|
|
117
|
+
if (railInputType !== 'touch') return
|
|
118
|
+
if (!phoneRailGesture(event.pointerType) || railPointer || (event.button ?? 0) !== 0) return
|
|
115
119
|
const pointerId = event.pointerId ?? 1
|
|
116
120
|
railPointer = {
|
|
117
121
|
pointerId,
|
|
@@ -159,7 +163,9 @@ export function createOperationsShell({
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
function onRailClick(event) {
|
|
162
|
-
|
|
166
|
+
const inputType = event?.pointerType || railInputType
|
|
167
|
+
railInputType = undefined
|
|
168
|
+
if (phoneRailGesture(inputType) && inputType === 'touch' && Number(event?.detail) > 0) {
|
|
163
169
|
event.preventDefault?.()
|
|
164
170
|
return
|
|
165
171
|
}
|
|
@@ -262,9 +268,19 @@ export function createOperationsShell({
|
|
|
262
268
|
}, 1000)
|
|
263
269
|
}
|
|
264
270
|
|
|
271
|
+
function flushSchemeProgress(pointer) {
|
|
272
|
+
if (pointer.frame !== undefined) window.cancelAnimationFrame(pointer.frame)
|
|
273
|
+
pointer.frame = undefined
|
|
274
|
+
if (pointer.progress !== undefined) {
|
|
275
|
+
pointer.gesture?.update?.(pointer.progress)
|
|
276
|
+
pointer.progress = undefined
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
265
280
|
function settleSchemePointer({ commit = false } = {}) {
|
|
266
281
|
const pointer = schemePointer
|
|
267
282
|
if (!pointer) return
|
|
283
|
+
flushSchemeProgress(pointer)
|
|
268
284
|
schemePointer = undefined
|
|
269
285
|
try { schemeToggle?.releasePointerCapture?.(pointer.pointerId) } catch {}
|
|
270
286
|
if (!pointer.dragging) return
|
|
@@ -313,19 +329,25 @@ export function createOperationsShell({
|
|
|
313
329
|
}
|
|
314
330
|
}
|
|
315
331
|
event.preventDefault?.()
|
|
316
|
-
pointer.
|
|
332
|
+
pointer.progress = schemePointerProgress(event)
|
|
333
|
+
if (pointer.frame === undefined) pointer.frame = window.requestAnimationFrame(() => {
|
|
334
|
+
pointer.frame = undefined
|
|
335
|
+
if (schemePointer === pointer) flushSchemeProgress(pointer)
|
|
336
|
+
})
|
|
317
337
|
}
|
|
318
338
|
|
|
319
339
|
function onSchemePointerUp(event) {
|
|
320
340
|
const pointer = schemePointer
|
|
321
341
|
if (!pointer || (event.pointerId ?? 1) !== pointer.pointerId) return
|
|
322
|
-
|
|
342
|
+
if (pointer.dragging) pointer.progress = schemePointerProgress(event)
|
|
343
|
+
const commit = pointer.dragging && schemePointerProgress(event) >= SCHEME_COMMIT_PROGRESS
|
|
323
344
|
settleSchemePointer({ commit })
|
|
324
345
|
}
|
|
325
346
|
|
|
326
347
|
function cancelSchemePointer() {
|
|
327
348
|
const pointer = schemePointer
|
|
328
349
|
if (!pointer) return
|
|
350
|
+
if (pointer.frame !== undefined) window.cancelAnimationFrame(pointer.frame)
|
|
329
351
|
if (pointer.dragging) {
|
|
330
352
|
try { pointer.gesture?.cancel?.() } catch {}
|
|
331
353
|
}
|
|
@@ -353,7 +375,7 @@ export function createOperationsShell({
|
|
|
353
375
|
const overlay = railMode === 'overlay'
|
|
354
376
|
const open = overlay && root.hasAttribute('data-prts-rail-open')
|
|
355
377
|
railLauncher.setAttribute('aria-expanded', String(open))
|
|
356
|
-
const closedLabel = phoneRailGesture() ? '
|
|
378
|
+
const closedLabel = phoneRailGesture() ? '打开 P.R.T.S. 导航(触摸时长按并下滑)' : '打开 P.R.T.S. 导航'
|
|
357
379
|
railLauncher.setAttribute('aria-label', open ? '关闭 P.R.T.S. 导航' : closedLabel)
|
|
358
380
|
if (overlay && !open) {
|
|
359
381
|
navRail.setAttribute('aria-hidden', 'true')
|
|
@@ -418,12 +440,16 @@ export function createOperationsShell({
|
|
|
418
440
|
}
|
|
419
441
|
|
|
420
442
|
function onResponsiveChange() {
|
|
443
|
+
railInputType = undefined
|
|
421
444
|
clearRailGesture()
|
|
422
445
|
setRailViewport(window.innerWidth)
|
|
423
446
|
syncRailState()
|
|
424
447
|
}
|
|
425
448
|
|
|
449
|
+
let railPhoneQuery
|
|
426
450
|
function bindResponsiveQueries() {
|
|
451
|
+
railPhoneQuery = window.matchMedia?.('(max-width: 640px)')
|
|
452
|
+
railPhoneQuery?.addEventListener?.('change', onResponsiveChange)
|
|
427
453
|
railViewportQuery = window.matchMedia?.('(min-width: 1180px)')
|
|
428
454
|
railPointerQuery = window.matchMedia?.('(pointer: coarse)')
|
|
429
455
|
railHoverQuery = window.matchMedia?.('(hover: none)')
|
|
@@ -507,6 +533,9 @@ export function createOperationsShell({
|
|
|
507
533
|
schemeToggle?.removeEventListener('lostpointercapture', onSchemeLostPointerCapture)
|
|
508
534
|
window.removeEventListener?.('blur', cancelSchemePointer)
|
|
509
535
|
themeDisable?.removeEventListener('click', disableTheme)
|
|
536
|
+
railPhoneQuery?.removeEventListener?.('change', onResponsiveChange)
|
|
537
|
+
railPhoneQuery = undefined
|
|
538
|
+
railInputType = undefined
|
|
510
539
|
railViewportQuery?.removeEventListener?.('change', onResponsiveChange)
|
|
511
540
|
railPointerQuery?.removeEventListener?.('change', onResponsiveChange)
|
|
512
541
|
railHoverQuery?.removeEventListener?.('change', onResponsiveChange)
|
|
@@ -42,7 +42,9 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
42
42
|
particlePattern: 'orthogonal',
|
|
43
43
|
railDefaultHidden: false,
|
|
44
44
|
sessionPreview: true,
|
|
45
|
+
aiToolsEnabled: true,
|
|
45
46
|
previewOngoing: true,
|
|
47
|
+
previewTools: true,
|
|
46
48
|
previewWarning: true,
|
|
47
49
|
previewDone: true,
|
|
48
50
|
previewFollowOngoing: true,
|
|
@@ -101,7 +103,8 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
101
103
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
102
104
|
material: Object.freeze(['glass', 'composerGlass']),
|
|
103
105
|
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
104
|
-
|
|
106
|
+
aiTools: Object.freeze(['aiToolsEnabled']),
|
|
107
|
+
navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
|
|
105
108
|
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'bakerLoading', 'conversationEmblem']),
|
|
106
109
|
})
|
|
107
110
|
|
|
@@ -237,7 +240,9 @@ export function normalizePreferences(value) {
|
|
|
237
240
|
sessionFlowColorFront: sessionFlowColors.front,
|
|
238
241
|
sessionFlowSpeed: sessionFlowSpeedOr(input.sessionFlowSpeed, DEFAULT_PREFERENCES.sessionFlowSpeed),
|
|
239
242
|
sessionPreview: booleanOr(input.sessionPreview, DEFAULT_PREFERENCES.sessionPreview),
|
|
243
|
+
aiToolsEnabled: booleanOr(input.aiToolsEnabled, DEFAULT_PREFERENCES.aiToolsEnabled),
|
|
240
244
|
previewOngoing: booleanOr(input.previewOngoing, DEFAULT_PREFERENCES.previewOngoing),
|
|
245
|
+
previewTools: booleanOr(input.previewTools, DEFAULT_PREFERENCES.previewTools),
|
|
241
246
|
previewWarning: booleanOr(input.previewWarning, DEFAULT_PREFERENCES.previewWarning),
|
|
242
247
|
previewDone: booleanOr(input.previewDone, DEFAULT_PREFERENCES.previewDone),
|
|
243
248
|
previewReplyLimit: [0, 5, 10, '0', '5', '10'].includes(input.previewReplyLimit) ? Number(input.previewReplyLimit) : 5,
|
|
@@ -39,6 +39,60 @@ export function latestAssistantReply(snapshot) {
|
|
|
39
39
|
return ''
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Call objects may be mutable on older hosts; compare raw arguments before reuse.
|
|
43
|
+
const toolSummaryCache = new WeakMap()
|
|
44
|
+
function toolSummary(call) {
|
|
45
|
+
const raw = call.argsRaw ?? call.call?.argsRaw ?? ''
|
|
46
|
+
const cached = toolSummaryCache.get(call)
|
|
47
|
+
if (cached?.raw === raw) return cached.text
|
|
48
|
+
let args
|
|
49
|
+
try { args = JSON.parse(raw) } catch {}
|
|
50
|
+
const summary = ['description', 'command', 'cmd', 'file_path', 'filePath', 'path', 'query', 'pattern', 'url']
|
|
51
|
+
.map(field => args?.[field]).find(value => typeof value === 'string' && value.trim())
|
|
52
|
+
const normalized = summary?.replace(/\s+/g, ' ').trim()
|
|
53
|
+
const text = normalized ? normalized.slice(0, 160) + (normalized.length > 160 ? '…' : '') : ''
|
|
54
|
+
toolSummaryCache.set(call, {raw, text})
|
|
55
|
+
return text
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Only calls after the latest settled prose belong to the current progress preview.
|
|
59
|
+
export function previewToolCalls(snapshot) {
|
|
60
|
+
const turn = snapshot?.timeline?.turnOrder?.at(-1)
|
|
61
|
+
const order = snapshot?.order ?? []
|
|
62
|
+
const calls = [], seen = new Set()
|
|
63
|
+
const visit = (call, key) => {
|
|
64
|
+
if (!call) return
|
|
65
|
+
const id = call.callId ?? key
|
|
66
|
+
if (!seen.has(id)) { seen.add(id); calls.push({call, key: id}) }
|
|
67
|
+
for (const [index, child] of (call.subCalls ?? []).entries()) visit(child, key + ':' + index)
|
|
68
|
+
}
|
|
69
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
70
|
+
const node = snapshot.nodes?.get?.(order[i])
|
|
71
|
+
if (['user', 'steering', 'turn-trigger'].includes(node?.kind)) break
|
|
72
|
+
if (turn !== undefined && node?.location?.turn?.turn !== turn) continue
|
|
73
|
+
if (node?.visibility === 'hidden') continue
|
|
74
|
+
if (node?.kind === 'assistant-step' && node.data?.status === 'settled'
|
|
75
|
+
&& node.data.blocks?.some(block => block.kind === 'text' && block.text?.trim())) break
|
|
76
|
+
if (node?.kind === 'tool-call') visit(node.data?.root, order[i])
|
|
77
|
+
}
|
|
78
|
+
return calls.sort((a, b) => (b.call.callTime ?? b.call.time ?? 0) - (a.call.callTime ?? a.call.time ?? 0)).map(({call, key}) => {
|
|
79
|
+
const text = toolSummary(call)
|
|
80
|
+
return {
|
|
81
|
+
key,
|
|
82
|
+
name: call.name || call.call?.name || '工具调用',
|
|
83
|
+
status: call.kind === 'tool-result' ? call.isError ? '失败' : '已完成'
|
|
84
|
+
: call.phase === 'preparing' ? '准备中' : '执行中',
|
|
85
|
+
summary: text ? text
|
|
86
|
+
: call.phase === 'preparing' ? '正在准备调用…' : '暂无操作摘要',
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function latestToolCall(snapshot) {
|
|
92
|
+
const records = previewToolCalls(snapshot)
|
|
93
|
+
return records.length ? {...records[0], records} : undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
42
96
|
export function completedPreviewReplies(snapshot) {
|
|
43
97
|
const replies = []
|
|
44
98
|
const latestTurn = snapshot?.timeline?.turnOrder?.at(-1)
|
|
@@ -142,8 +196,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
142
196
|
active = undefined
|
|
143
197
|
if (!old) return
|
|
144
198
|
window.clearTimeout(old.leaveTimer)
|
|
145
|
-
window.
|
|
146
|
-
window.
|
|
199
|
+
window.cancelAnimationFrame(old.delay)
|
|
200
|
+
window.cancelAnimationFrame(old.refresh)
|
|
147
201
|
old.unsubscribe?.()
|
|
148
202
|
old.unsubscribeStatus?.()
|
|
149
203
|
if (keepWarm && old.reference && old.source) {
|
|
@@ -311,6 +365,39 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
311
365
|
body.setAttribute('data-prts-preview-body', '')
|
|
312
366
|
panel.querySelector('p').replaceWith(body)
|
|
313
367
|
}
|
|
368
|
+
panel.querySelector('[data-prts-preview-tool]')?.remove()
|
|
369
|
+
const toolSection = panel.ownerDocument.createElement('div')
|
|
370
|
+
toolSection.setAttribute('data-prts-preview-tool', '')
|
|
371
|
+
toolSection.hidden = true
|
|
372
|
+
body.before(toolSection)
|
|
373
|
+
let previousTool, currentTool
|
|
374
|
+
const toolEntry = panel.ownerDocument.createElement('button')
|
|
375
|
+
toolEntry.type = 'button'
|
|
376
|
+
toolEntry.setAttribute('data-prts-tool-entry', '')
|
|
377
|
+
const toolHeading = panel.ownerDocument.createElement('small')
|
|
378
|
+
const toolName = panel.ownerDocument.createElement('span')
|
|
379
|
+
toolEntry.append(toolHeading, toolName)
|
|
380
|
+
toolSection.append(toolEntry)
|
|
381
|
+
const openToolDetail = (refresh = false) => {
|
|
382
|
+
if (currentTool) showDetail({key: 'latest-tool', tool: currentTool}, 0, toolEntry, refresh)
|
|
383
|
+
}
|
|
384
|
+
toolEntry.addEventListener('pointermove', () => openToolDetail())
|
|
385
|
+
toolEntry.addEventListener('focus', () => openToolDetail())
|
|
386
|
+
toolEntry.addEventListener('click', () => openToolDetail())
|
|
387
|
+
const renderTool = tool => {
|
|
388
|
+
const signature = JSON.stringify(tool)
|
|
389
|
+
if (signature === previousTool) return
|
|
390
|
+
previousTool = signature
|
|
391
|
+
currentTool = tool
|
|
392
|
+
toolSection.hidden = !tool
|
|
393
|
+
if (!tool) {
|
|
394
|
+
if (detailKey === 'latest-tool') hideDetail()
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
toolHeading.textContent = '工具 · ' + tool.status + ' · ' + tool.records.length + ' 次'
|
|
398
|
+
toolName.textContent = tool.name
|
|
399
|
+
if (detailKey === 'latest-tool') openToolDetail(true)
|
|
400
|
+
}
|
|
314
401
|
const choices = panel.ownerDocument.createElement('div')
|
|
315
402
|
choices.setAttribute('data-prts-preview-choices', '')
|
|
316
403
|
panel.querySelector('[data-prts-preview-choices]')?.remove()
|
|
@@ -450,9 +537,9 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
450
537
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
451
538
|
detailEntry = undefined
|
|
452
539
|
}
|
|
453
|
-
const showDetail = (reply, index, item) => {
|
|
540
|
+
const showDetail = (reply, index, item, refresh = false) => {
|
|
454
541
|
keep()
|
|
455
|
-
if (detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
542
|
+
if (!refresh && detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
456
543
|
if (!current.detail) {
|
|
457
544
|
const detail = panel.ownerDocument.createElement('section')
|
|
458
545
|
detail.setAttribute('data-prts-summary-reply', '')
|
|
@@ -472,10 +559,36 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
472
559
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
473
560
|
detailEntry = item
|
|
474
561
|
item?.setAttribute('data-prts-reply-active', '')
|
|
475
|
-
detail.
|
|
562
|
+
detail.toggleAttribute('data-prts-tool-history', Boolean(reply.tool))
|
|
563
|
+
detail.querySelector('strong').textContent = reply.tool ? '本段工具记录 · ' + reply.tool.records.length + ' 次' : '回复 ' + (reply.ordinal ?? index + 1)
|
|
476
564
|
const content = detail.querySelector('[data-prts-preview-body]')
|
|
477
|
-
content.
|
|
478
|
-
|
|
565
|
+
const scrollTop = refresh ? content.scrollTop : 0
|
|
566
|
+
let anchorKey, anchorOffset
|
|
567
|
+
if (refresh && scrollTop > 0) {
|
|
568
|
+
const anchor = [...content.children].find(row => row.getBoundingClientRect().bottom > content.getBoundingClientRect().top)
|
|
569
|
+
anchorKey = anchor?.dataset.toolKey
|
|
570
|
+
anchorOffset = anchor?.getBoundingClientRect().top
|
|
571
|
+
}
|
|
572
|
+
if (reply.tool) {
|
|
573
|
+
content.replaceChildren(...reply.tool.records.map(record => {
|
|
574
|
+
const row = panel.ownerDocument.createElement('article')
|
|
575
|
+
row.setAttribute('data-prts-tool-record', '')
|
|
576
|
+
row.dataset.toolKey = record.key
|
|
577
|
+
const heading = panel.ownerDocument.createElement('div')
|
|
578
|
+
const name = panel.ownerDocument.createElement('b')
|
|
579
|
+
name.textContent = record.name
|
|
580
|
+
const status = panel.ownerDocument.createElement('small')
|
|
581
|
+
status.textContent = record.status
|
|
582
|
+
heading.append(name, status)
|
|
583
|
+
const summary = panel.ownerDocument.createElement('p')
|
|
584
|
+
summary.textContent = record.summary
|
|
585
|
+
row.append(heading, summary)
|
|
586
|
+
return row
|
|
587
|
+
}))
|
|
588
|
+
} else content.innerHTML = current.contentCache(reply.text).html
|
|
589
|
+
content.scrollTop = scrollTop
|
|
590
|
+
const retainedAnchor = anchorKey && [...content.children].find(row => row.dataset.toolKey === anchorKey)
|
|
591
|
+
if (retainedAnchor) content.scrollTop += retainedAnchor.getBoundingClientRect().top - anchorOffset
|
|
479
592
|
detail.hidden = false
|
|
480
593
|
const rect = panel.getBoundingClientRect()
|
|
481
594
|
const width = Math.min(340, window.innerWidth - 16)
|
|
@@ -548,11 +661,18 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
548
661
|
}))
|
|
549
662
|
}
|
|
550
663
|
choices.hidden = !labels.length
|
|
664
|
+
const tool = state === 'ongoing' && !question && getPreviewPreferences?.().previewTools !== false ? latestToolCall(source?.getSnapshot()) : undefined
|
|
665
|
+
renderTool(tool)
|
|
666
|
+
if (tool) label.textContent = '运行中 · 最新活动'
|
|
551
667
|
panel.setAttribute('aria-label', title + ':' + label.textContent)
|
|
552
668
|
if (state === 'ongoing' && source) {
|
|
553
669
|
const replies = selectReplies(source.getSnapshot(), replyLimit())
|
|
554
|
-
|
|
555
|
-
|
|
670
|
+
panel.toggleAttribute('data-prts-preview-empty', !replies.length && !tool)
|
|
671
|
+
if (replies === historyKeys) {
|
|
672
|
+
const emptyText = tool ? '' : '正在处理…'
|
|
673
|
+
if (!replies.length && body.textContent !== emptyText) body.textContent = emptyText
|
|
674
|
+
return
|
|
675
|
+
}
|
|
556
676
|
const previousTop = body.scrollTop
|
|
557
677
|
const scrollAnchor = previousTop > 0 ? [...body.children].find(entry => entry.getBoundingClientRect().bottom > body.getBoundingClientRect().top) : undefined
|
|
558
678
|
const anchorTop = scrollAnchor?.getBoundingClientRect().top
|
|
@@ -568,7 +688,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
568
688
|
}
|
|
569
689
|
}
|
|
570
690
|
if (!historyKeys.length) body.replaceChildren()
|
|
571
|
-
if (!replies.length) body.textContent = '
|
|
691
|
+
if (!replies.length) body.textContent = tool ? '' : '正在处理…'
|
|
572
692
|
for (let index = 0; index < replies.length; index++) {
|
|
573
693
|
const reply = replies[index]
|
|
574
694
|
if (retained.has(reply.key)) continue
|
|
@@ -580,6 +700,13 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
580
700
|
item.setAttribute('data-prts-reply-entry', '')
|
|
581
701
|
const heading = panel.ownerDocument.createElement('small')
|
|
582
702
|
heading.textContent = '回复 ' + reply.ordinal
|
|
703
|
+
const icon = panel.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
704
|
+
icon.setAttribute('viewBox', '0 0 16 16')
|
|
705
|
+
icon.setAttribute('aria-hidden', 'true')
|
|
706
|
+
const outline = panel.ownerDocument.createElementNS(icon.namespaceURI, 'path')
|
|
707
|
+
outline.setAttribute('d', 'M3 2.5h10a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7l-4 2v-2a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1Z')
|
|
708
|
+
icon.append(outline)
|
|
709
|
+
heading.prepend(icon)
|
|
583
710
|
const summary = panel.ownerDocument.createElement('span')
|
|
584
711
|
summary.textContent = current.contentCache(reply.text).summary
|
|
585
712
|
item.append(heading, summary)
|
|
@@ -610,7 +737,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
610
737
|
? approval
|
|
611
738
|
? [pending.toolName, pending.displayReason?.['zh-CN'] || pending.displayReason?.zh || pending.reason || pending.displayReason?.en || '请进入会话查看操作详情'].filter(Boolean).join(':')
|
|
612
739
|
: first?.question || pendingQuestionPreview(pending)
|
|
613
|
-
: source ? excerpt || (state === 'ongoing' ? '
|
|
740
|
+
: source ? excerpt || (state === 'ongoing' ? '正在处理…' : '暂无正文回复,可进入会话查看执行记录。') : '正在读取…'
|
|
614
741
|
if (previousText !== text || previousQuestion !== question) {
|
|
615
742
|
const extendsReply = previousQuestion === question && previousText && text.startsWith(previousText)
|
|
616
743
|
if (question) body.textContent = text
|
|
@@ -631,7 +758,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
631
758
|
})
|
|
632
759
|
render()
|
|
633
760
|
body.scrollTop = 0
|
|
634
|
-
current.delay = window.
|
|
761
|
+
current.delay = window.requestAnimationFrame(async () => {
|
|
762
|
+
current.delay = undefined
|
|
635
763
|
try {
|
|
636
764
|
const service = getUiConversation?.()
|
|
637
765
|
const controller = getSessions?.() ?? sessions
|
|
@@ -643,11 +771,13 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
643
771
|
current.source = source
|
|
644
772
|
current.unsubscribe = source.subscribe(() => {
|
|
645
773
|
if (active !== current || current.refresh !== undefined) return
|
|
646
|
-
current.refresh = window.
|
|
774
|
+
current.refresh = window.requestAnimationFrame(() => {
|
|
647
775
|
current.refresh = undefined
|
|
648
|
-
if (current.state === 'ongoing' && !current.editor
|
|
776
|
+
if (current.state === 'ongoing' && !current.editor
|
|
777
|
+
&& selectReplies(source.getSnapshot(), replyLimit()) === historyKeys
|
|
778
|
+
&& JSON.stringify(getPreviewPreferences?.().previewTools === false ? undefined : latestToolCall(source.getSnapshot())) === previousTool) return
|
|
649
779
|
render()
|
|
650
|
-
}
|
|
780
|
+
})
|
|
651
781
|
})
|
|
652
782
|
render()
|
|
653
783
|
} catch {
|
|
@@ -659,7 +789,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
659
789
|
render()
|
|
660
790
|
if (body.textContent.trim() === '正在读取…') body.textContent = '暂时无法读取回复,可点击会话查看'
|
|
661
791
|
}
|
|
662
|
-
}
|
|
792
|
+
})
|
|
663
793
|
}
|
|
664
794
|
function leave() {
|
|
665
795
|
if (!active || active.sticky) return
|
|
@@ -673,6 +803,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
673
803
|
if (!active) return
|
|
674
804
|
const anchor = [...root.querySelectorAll('[data-prts-session-summary-item]')]
|
|
675
805
|
.find(item => item.getAttribute('data-prts-session-summary-item') === active.id)
|
|
806
|
+
// A read completion (or removed session) has no status row to own a preview.
|
|
807
|
+
if (!anchor) { stop(); return }
|
|
676
808
|
active.group?.removeAttribute('data-prts-preview-open')
|
|
677
809
|
active.anchor?.removeAttribute('data-prts-preview-active')
|
|
678
810
|
active.anchor = anchor
|
|
@@ -976,7 +976,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
976
976
|
|
|
977
977
|
return {
|
|
978
978
|
start(preferences = {}) {
|
|
979
|
-
const keys = ['sessionPreview','previewOngoing','previewWarning','previewDone','previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']
|
|
979
|
+
const keys = ['sessionPreview','previewOngoing','previewTools','previewWarning','previewDone','previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']
|
|
980
980
|
if (keys.some(key => previewPreferences[key] !== preferences[key])) preview.stop()
|
|
981
981
|
previewPreferences = Object.fromEntries(keys.map(key => [key, preferences[key]]))
|
|
982
982
|
if (!document) return
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createCursorAdapter } from './cursor-adapter.js'
|
|
2
2
|
import { normalizePreferences } from './preferences.js'
|
|
3
3
|
|
|
4
|
+
export const SCHEME_COMMIT_PROGRESS = .15
|
|
5
|
+
|
|
4
6
|
const ROOT_ATTRIBUTES = [
|
|
5
7
|
'data-dsh-prts',
|
|
6
8
|
'data-prts-scheme',
|
|
@@ -244,11 +246,23 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
244
246
|
function updateRevealHud(session) {
|
|
245
247
|
if (!session || session.settled) return
|
|
246
248
|
const percentage = Math.round(revealProgress(session.progress) * 100)
|
|
247
|
-
if (session.hudEdge) session.hudEdge.dataset.prtsSchemeRevealSide = 'left'
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
if (session.hudEdge && session.hudEdge.dataset.prtsSchemeRevealSide !== 'left') session.hudEdge.dataset.prtsSchemeRevealSide = 'left'
|
|
250
|
+
if (session.hudPercentage !== percentage) {
|
|
251
|
+
session.hudPercentage = percentage
|
|
252
|
+
const image = revealValueImageUrl(percentage)
|
|
253
|
+
if (session.hudAnimation?.effect?.setKeyframes) {
|
|
254
|
+
const frame = {
|
|
255
|
+
backgroundImage: image + ',linear-gradient(#ffd400,#ffd400)',
|
|
256
|
+
backgroundSize: '24px 11px,2px ' + (percentage * .48) + 'px',
|
|
257
|
+
}
|
|
258
|
+
session.hudAnimation.effect.setKeyframes([frame, frame])
|
|
259
|
+
} else {
|
|
260
|
+
root.style.setProperty('--prts-scheme-control-meter-height', `${percentage * .48}px`)
|
|
261
|
+
root.style.setProperty('--prts-scheme-control-value-image', image)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const armed = session.interactive && session.progress >= SCHEME_COMMIT_PROGRESS
|
|
265
|
+
if (root.hasAttribute(TRANSITION_ARMED_ATTRIBUTE) !== armed) root.toggleAttribute(TRANSITION_ARMED_ATTRIBUTE, armed)
|
|
252
266
|
}
|
|
253
267
|
|
|
254
268
|
function releaseRevealHud(session) {
|
|
@@ -361,11 +375,16 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
361
375
|
], options, '::view-transition-new(prts-scheme-grid)')
|
|
362
376
|
const mark = revealNamedAnimation([
|
|
363
377
|
{ offset: 0, translate: revealGroupTranslate(0), rotate: '45deg' },
|
|
364
|
-
{ offset: .
|
|
365
|
-
{ offset: .
|
|
378
|
+
{ offset: SCHEME_COMMIT_PROGRESS - .01, translate: revealGroupTranslate(session.geometry.width * (SCHEME_COMMIT_PROGRESS - .01)), rotate: '45deg' },
|
|
379
|
+
{ offset: SCHEME_COMMIT_PROGRESS + .01, translate: revealGroupTranslate(session.geometry.width * (SCHEME_COMMIT_PROGRESS + .01)), rotate: '225deg' },
|
|
366
380
|
{ offset: 1, translate: revealGroupTranslate(session.geometry.width), rotate: '225deg' },
|
|
367
381
|
], options, '::view-transition-group(prts-scheme-mark)')
|
|
368
382
|
session.scrubAnimations = [clip, edge, node, grid, gridPhase, mark]
|
|
383
|
+
session.hudAnimation = revealNamedAnimation([{}], options, '::view-transition-new(prts-scheme-node)')
|
|
384
|
+
session.hudAnimation.pause?.()
|
|
385
|
+
session.hudAnimation.currentTime = 0
|
|
386
|
+
session.hudPercentage = undefined
|
|
387
|
+
updateRevealHud(session)
|
|
369
388
|
for (const animation of session.scrubAnimations) {
|
|
370
389
|
animation.pause?.()
|
|
371
390
|
animation.currentTime = session.progress * session.duration
|
|
@@ -377,11 +396,10 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
377
396
|
if (!session || session.settled) return
|
|
378
397
|
session.progress = revealProgress(value)
|
|
379
398
|
session.revealX = session.progress * session.geometry.width
|
|
380
|
-
root.style.setProperty('--prts-scheme-reveal-x', `${session.revealX}px`)
|
|
399
|
+
if (!session.scrubAnimations) root.style.setProperty('--prts-scheme-reveal-x', `${session.revealX}px`)
|
|
381
400
|
if (session.scrubAnimations) {
|
|
382
401
|
try {
|
|
383
402
|
for (const animation of session.scrubAnimations) {
|
|
384
|
-
animation.pause?.()
|
|
385
403
|
animation.currentTime = session.progress * session.duration
|
|
386
404
|
}
|
|
387
405
|
} catch {}
|
|
@@ -402,12 +420,19 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
402
420
|
for (const animation of session?.scrubAnimations || []) {
|
|
403
421
|
try { animation?.cancel?.() } catch {}
|
|
404
422
|
}
|
|
405
|
-
|
|
423
|
+
session?.hudAnimation?.cancel?.()
|
|
424
|
+
if (session) {
|
|
425
|
+
session.scrubAnimations = undefined
|
|
426
|
+
session.hudAnimation = undefined
|
|
427
|
+
session.hudPercentage = undefined
|
|
428
|
+
}
|
|
406
429
|
}
|
|
407
430
|
|
|
408
431
|
function animateRevealTo(session, value, duration, easing) {
|
|
409
432
|
if (!session || session.settled) return Promise.resolve(false)
|
|
410
433
|
cancelRevealAnimation(session)
|
|
434
|
+
// Transfer the last HUD value once before the release animation takes over.
|
|
435
|
+
updateRevealHud(session)
|
|
411
436
|
const targetProgress = revealProgress(value)
|
|
412
437
|
const targetX = revealTargetX(session, targetProgress)
|
|
413
438
|
if (!(duration > 0)) {
|
|
@@ -449,8 +474,8 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
449
474
|
{ backgroundSize: `2px ${targetProgress * 48}px` },
|
|
450
475
|
], options, '::view-transition-new(prts-scheme-node)')
|
|
451
476
|
const mark = revealNamedAnimation([
|
|
452
|
-
{ translate: revealGroupTranslate(session.revealX), rotate: session.progress >=
|
|
453
|
-
{ translate: revealGroupTranslate(targetX), rotate: targetProgress >=
|
|
477
|
+
{ translate: revealGroupTranslate(session.revealX), rotate: session.progress >= SCHEME_COMMIT_PROGRESS ? '225deg' : '45deg' },
|
|
478
|
+
{ translate: revealGroupTranslate(targetX), rotate: targetProgress >= SCHEME_COMMIT_PROGRESS ? '225deg' : '45deg' },
|
|
454
479
|
], options, '::view-transition-group(prts-scheme-mark)')
|
|
455
480
|
animations = [clip, edge, node, grid, gridPhase, nodeProgress, mark]
|
|
456
481
|
} catch {
|
|
@@ -608,7 +633,7 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
608
633
|
let settled = false
|
|
609
634
|
return {
|
|
610
635
|
update(value) { if (!settled) progress = revealProgress(value) },
|
|
611
|
-
finish(commit = progress >=
|
|
636
|
+
finish(commit = progress >= SCHEME_COMMIT_PROGRESS) {
|
|
612
637
|
if (settled) return root.dataset.prtsScheme
|
|
613
638
|
settled = true
|
|
614
639
|
return commit ? setTheme(target, { animate: false }) : source
|