@ash-qw/dsh-theme-prts 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { createSessionSummaryPreview, scrollSummaryTitle } from './session-summary-preview.js'
1
2
  import { ensureFacilityGraphic, ensureFacilityTextureDefs, renderFacilityGraphic } from './facility-vector.js'
2
3
  import { createSessionLifelineAnimator } from './session-lifeline.js'
3
4
 
@@ -189,7 +190,8 @@ function ensureFacilitySpine(document, row, kind, next) {
189
190
  }
190
191
 
191
192
 
192
- export function createSidebarControlAdapter({ document, window, sessions, workspaces, getSessionStatus, getUiWorkspace }) {
193
+ export function createSidebarControlAdapter({ document, window, sessions, workspaces, getSessionStatus, getUiWorkspace, getUiConversation, getSessions }) {
194
+ let previewPreferences = {}
193
195
  let observer
194
196
  let resizeObserver
195
197
  let resizeFrame
@@ -201,6 +203,21 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
201
203
  let statusSourcesStarted = false
202
204
  let summaryRoot
203
205
  let summarySignature = ''
206
+ let heldPreviewState
207
+ function dismissHeldPreview(event) {
208
+ if (!heldPreviewState || (event?.type === 'keydown' && event.key !== 'Escape')) return
209
+ if (event?.type === 'pointerdown' && (summaryRoot?.contains(event.target) || event.target.closest?.('[data-prts-summary-reply]'))) return
210
+ heldPreviewState = undefined
211
+ queueScan()
212
+ }
213
+ function enterPreviewState(state) {
214
+ if (heldPreviewState && heldPreviewState !== state) { heldPreviewState = undefined; queueScan() }
215
+ preview.enterState(state)
216
+ }
217
+ const preview = createSessionSummaryPreview({ document, window, sessions, getUiConversation, getSessions, getSessionStatus, openSession, getPreviewPreferences: () => previewPreferences, onPreviewClosed: () => { if (statusSourcesStarted) queueScan() }, onAnswerSubmitted: state => { heldPreviewState = state; queueScan() } })
218
+ const stopTitles = new Map()
219
+ const summaryGroups = new Map()
220
+ function clearSummaryEffects() { preview.stop(false); for (const stop of stopTitles.values()) stop(); stopTitles.clear(); summaryGroups.clear() }
204
221
  let owned = new Set()
205
222
  let vectorOwners = new Set()
206
223
  const dirtyVectors = new Map()
@@ -216,6 +233,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
216
233
  }
217
234
 
218
235
  function openSession(sessionId) {
236
+ dismissHeldPreview()
219
237
  try {
220
238
  const uiWorkspace = getUiWorkspace?.()
221
239
  if (typeof uiWorkspace?.openSession === 'function') {
@@ -256,6 +274,8 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
256
274
  count.setAttribute('data-prts-session-summary-count', '')
257
275
  count.textContent = String(items.length)
258
276
  trigger.append(count)
277
+ trigger.addEventListener('pointerenter', () => enterPreviewState(state))
278
+ trigger.addEventListener('focus', () => enterPreviewState(state))
259
279
 
260
280
  const popover = document.createElement('span')
261
281
  popover.setAttribute('data-prts-session-summary-popover', state)
@@ -269,15 +289,73 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
269
289
  hint.textContent = '选择会话'
270
290
  heading.append(headingLabel, hint)
271
291
  popover.append(heading)
272
- for (const item of items) {
292
+ const reply = document.createElement('section')
293
+ reply.setAttribute('data-prts-summary-reply', '')
294
+ reply.setAttribute('role', 'note')
295
+ reply.setAttribute('popover', 'manual')
296
+ reply.hidden = true
297
+ reply.append(document.createElement('strong'), document.createElement('p'))
298
+ group.addEventListener('pointerleave', () => preview.leave())
299
+ group.addEventListener('focusout', event => {
300
+ if (!group.contains(event.relatedTarget)) preview.leave()
301
+ })
302
+ const list = document.createElement('span')
303
+ list.setAttribute('data-prts-summary-list', '')
304
+ popover.append(list)
305
+ const entries = new Map()
306
+ const update = items => {
307
+ count.textContent = String(items.length)
308
+ trigger.setAttribute('aria-label', `${label} ${items.length}`)
309
+ const ids = new Set(items.map(item => item.id))
310
+ for (const [id, entry] of entries) {
311
+ if (ids.has(id)) continue
312
+ stopTitles.get(entry.button)?.()
313
+ stopTitles.delete(entry.button)
314
+ entry.button.remove()
315
+ entries.delete(id)
316
+ }
317
+ for (const [index, incoming] of items.entries()) {
318
+ const existing = entries.get(incoming.id)
319
+ if (existing) {
320
+ if (existing.item.title !== incoming.title) {
321
+ stopTitles.get(existing.button)?.()
322
+ existing.button.querySelector('[data-prts-summary-title] > span').textContent = incoming.title
323
+ }
324
+ Object.assign(existing.item, incoming)
325
+ let detail = existing.button.querySelector('[data-prts-session-summary-item-status]')
326
+ if (incoming.statusLabel && !detail) { detail = document.createElement('small'); detail.setAttribute('data-prts-session-summary-item-status',''); existing.button.append(detail) }
327
+ if (incoming.statusLabel) detail.textContent = incoming.statusLabel
328
+ else detail?.remove()
329
+ existing.button.setAttribute('aria-label', incoming.statusLabel ? `${incoming.title},${incoming.statusLabel}` : incoming.title)
330
+ if (list.children[index] !== existing.button) list.insertBefore(existing.button, list.children[index] ?? null)
331
+ continue
332
+ }
333
+ const item = {...incoming}
273
334
  const button = document.createElement('button')
274
335
  button.type = 'button'
275
336
  button.setAttribute('role', 'menuitem')
276
337
  button.setAttribute('data-prts-session-summary-item', item.id)
277
338
  button.append(statusSignal(document, state))
278
339
  const title = document.createElement('span')
279
- title.textContent = item.title
340
+ title.setAttribute('data-prts-summary-title', '')
341
+ const text = document.createElement('span')
342
+ text.textContent = item.title
343
+ title.append(text)
280
344
  button.append(title)
345
+ const viewing = document.createElement('em')
346
+ viewing.setAttribute('data-prts-preview-marker', '')
347
+ viewing.textContent = '预览中'
348
+ button.append(viewing)
349
+ button.setAttribute('aria-label', item.title)
350
+ stopTitles.set(button, scrollSummaryTitle(button, title, text, window))
351
+ const showReply = () => {
352
+ const key = {ongoing:'previewOngoing',warning:'previewWarning',done:'previewDone'}[state]
353
+ if (previewPreferences[key] === false) { preview.stop(); return }
354
+ preview.show(item.id, reply, item.title, button)
355
+ }
356
+ button.addEventListener('pointermove', showReply)
357
+ button.addEventListener('pointerleave', () => preview.leave())
358
+ button.addEventListener('focus', showReply)
281
359
  if (item.statusLabel) {
282
360
  const detail = document.createElement('small')
283
361
  detail.setAttribute('data-prts-session-summary-item-status', '')
@@ -287,17 +365,29 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
287
365
  }
288
366
  button.addEventListener('click', event => {
289
367
  event.stopPropagation()
368
+ preview.stop()
290
369
  openSession(item.id)
291
370
  })
292
- popover.append(button)
371
+ list.insertBefore(button, list.children[index] ?? null)
372
+ entries.set(item.id, {item, button})
373
+ }
293
374
  }
375
+ update(items)
376
+ summaryGroups.set(state, {group, update, dispose: () => {
377
+ for (const {button} of entries.values()) { stopTitles.get(button)?.(); stopTitles.delete(button) }
378
+ group.remove()
379
+ }})
380
+ popover.append(reply)
294
381
  group.append(trigger, popover)
295
382
  return group
296
383
  }
297
384
 
298
385
  function syncSummary(root, next) {
299
386
  const { header, label } = resolveSectionHeader(root)
300
- if (!header || !label) {
387
+ if (previewPreferences.sessionPreview === false || !header || !label) {
388
+ heldPreviewState = undefined
389
+ preview.stop()
390
+ clearSummaryEffects()
301
391
  summaryRoot?.remove()
302
392
  summaryRoot = undefined
303
393
  summarySignature = ''
@@ -326,10 +416,31 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
326
416
  rows[index] = { ...rows[index], state: nativeRow.state }
327
417
  }
328
418
  }
329
- const warning = rows.filter(item => item.state === 'warning')
330
- const ongoing = rows.filter(item => item.state === 'ongoing')
331
- const done = rows.filter(item => item.state === 'done')
419
+ // Do not move the row/menu to another status behind a frozen preview.
420
+ const pinned = preview.pinnedState()
421
+ if (pinned?.state) {
422
+ const item = rows.find(item => item.id === pinned.id)
423
+ if (item) { item.state = pinned.state; item.statusLabel = undefined }
424
+ }
425
+ // While reading a preview, preserve surviving rows' positions and append
426
+ // newcomers. No persistent ordering: the next idle sync uses host order.
427
+ const orderedState = state => {
428
+ const items = rows.filter(item => item.state === state)
429
+ if (!preview.isOpen() || !summaryRoot) return items
430
+ const remaining = new Map(items.map(item => [item.id, item]))
431
+ const stable = []
432
+ for (const row of summaryRoot.querySelectorAll(`[data-prts-session-summary-group="${state}"] [data-prts-session-summary-item]`)) {
433
+ const id = row.getAttribute('data-prts-session-summary-item')
434
+ if (remaining.has(id)) { stable.push(remaining.get(id)); remaining.delete(id) }
435
+ }
436
+ return [...stable, ...remaining.values()]
437
+ }
438
+ const warning = orderedState('warning')
439
+ const ongoing = orderedState('ongoing')
440
+ const done = orderedState('done')
441
+ if (heldPreviewState && !rows.some(item => item.state === heldPreviewState)) heldPreviewState = undefined
332
442
  const signature = JSON.stringify({
443
+ heldPreviewState,
333
444
  warning: warning.map(item => [item.id, item.title, item.statusLabel]),
334
445
  ongoing: ongoing.map(item => [item.id, item.title]),
335
446
  done: done.map(item => [item.id, item.title]),
@@ -349,10 +460,20 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
349
460
  summaryRoot.hidden = warning.length === 0 && ongoing.length === 0 && done.length === 0
350
461
  if (signature === summarySignature) return
351
462
  summarySignature = signature
352
- summaryRoot.replaceChildren()
353
- if (warning.length) summaryRoot.append(buildSummaryGroup('warning', '等待处理的会话', warning))
354
- if (ongoing.length) summaryRoot.append(buildSummaryGroup('ongoing', '运行中的会话', ongoing))
355
- if (done.length) summaryRoot.append(buildSummaryGroup('done', '完成未查看', done))
463
+ for (const [state, title, items] of [['warning','等待处理的会话',warning],['ongoing','运行中的会话',ongoing],['done','完成未查看',done]]) {
464
+ const existing = summaryGroups.get(state)
465
+ if (!items.length) { existing?.dispose(); summaryGroups.delete(state); continue }
466
+ if (existing) existing.update(items)
467
+ else summaryRoot.append(buildSummaryGroup(state, title, items))
468
+ }
469
+ let position = 0
470
+ for (const state of ['warning','ongoing','done']) {
471
+ const group = summaryGroups.get(state)?.group
472
+ if (group && summaryRoot.children[position++] !== group) summaryRoot.insertBefore(group, summaryRoot.children[position - 1] ?? null)
473
+ group?.removeAttribute('data-prts-preview-open')
474
+ }
475
+ preview.reanchor(summaryRoot)
476
+ if (heldPreviewState) summaryRoot.querySelector(`[data-prts-session-summary-group="${heldPreviewState}"]`)?.setAttribute('data-prts-preview-open', '')
356
477
  }
357
478
 
358
479
  function scheduleVectors() {
@@ -487,6 +608,8 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
487
608
  }
488
609
  syncSummary(root, next)
489
610
  } else {
611
+ preview.stop()
612
+ clearSummaryEffects()
490
613
  summaryRoot?.remove()
491
614
  summaryRoot = undefined
492
615
  summarySignature = ''
@@ -508,6 +631,8 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
508
631
  }
509
632
 
510
633
  function clear() {
634
+ preview.stop()
635
+ clearSummaryEffects()
511
636
  lifelineAnimator.setTarget(undefined, undefined)
512
637
  for (const node of document?.querySelectorAll?.('[data-prts-owned-projection], [data-prts-owned-facility], [data-prts-owned-facility-spine], [data-prts-owned-facility-vector], [data-prts-owned-facility-defs]') ?? []) node.remove()
513
638
  for (const node of owned) {
@@ -525,7 +650,10 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
525
650
  }
526
651
 
527
652
  return {
528
- start() {
653
+ start(preferences = {}) {
654
+ const keys = ['sessionPreview','previewOngoing','previewWarning','previewDone','previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']
655
+ if (keys.some(key => previewPreferences[key] !== preferences[key])) preview.stop()
656
+ previewPreferences = Object.fromEntries(keys.map(key => [key, preferences[key]]))
529
657
  if (!document) return
530
658
  lifelineAnimator.start()
531
659
  ensureFacilityTextureDefs(document)
@@ -541,6 +669,8 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
541
669
  flushVectors()
542
670
  })
543
671
  }
672
+ document.addEventListener('pointerdown', dismissHeldPreview)
673
+ document.addEventListener('keydown', dismissHeldPreview)
544
674
  scan()
545
675
  if (!statusSourcesStarted) {
546
676
  statusSourcesStarted = true
@@ -550,7 +680,9 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
550
680
  if (observer || !window?.MutationObserver) return
551
681
  const observationRoot = document.querySelector('[data-prts-region="sessions"]')
552
682
  if (!observationRoot) return
553
- observer = new window.MutationObserver(queueScan)
683
+ observer = new window.MutationObserver(mutations => {
684
+ if (mutations.some(mutation => !mutation.target.closest?.('[data-prts-owned-session-summary]'))) queueScan()
685
+ })
554
686
  observer.observe(observationRoot, {
555
687
  subtree: true,
556
688
  childList: true,
@@ -559,6 +691,9 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
559
691
  })
560
692
  },
561
693
  dispose() {
694
+ heldPreviewState = undefined
695
+ document?.removeEventListener('pointerdown', dismissHeldPreview)
696
+ document?.removeEventListener('keydown', dismissHeldPreview)
562
697
  observer?.disconnect()
563
698
  observer = undefined
564
699
  stopSessionList()
@@ -40,6 +40,10 @@ const PARTICLE_OPTIONS = [
40
40
  ['precise', '精细', 18],
41
41
  ]
42
42
 
43
+ function previewSetting(key, label, description) {
44
+ return settingButtons(key, label, []).replace('</strong>', `</strong><small data-prts-preview-setting-description>${description}</small>`)
45
+ }
46
+
43
47
  function settingButtons(key, label, options) {
44
48
  const note = key === 'glass'
45
49
  ? '<small data-prts-setting-note data-prts-transparency-status hidden>系统当前要求降低透明度</small>'
@@ -60,7 +64,7 @@ function settingButtons(key, label, options) {
60
64
  : key === 'sessionFlowPalette'
61
65
  ? '<small data-prts-setting-note>同时适配日间与夜间模式</small>'
62
66
  : ''
63
- if (['cursor', 'railDefaultHidden', 'sessionFlow', 'sessionGlow', 'bakerGroupAvatars', 'conversationEmblem'].includes(key)) return `<div data-prts-setting-row="${key}">
67
+ if (['cursor', 'railDefaultHidden', 'sessionFlow', 'sessionGlow', 'bakerGroupAvatars', 'conversationEmblem', 'sessionPreview', 'previewOngoing', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning'].includes(key)) return `<div data-prts-setting-row="${key}">
64
68
  <span><strong>${label}</strong>${note}</span>
65
69
  <button type="button" role="switch" aria-label="${label}" aria-checked="false" data-prts-setting-switch="${key}" ${key === 'cursor' ? 'data-prts-cursor-switch' : ''}><span aria-hidden="true"></span></button>
66
70
  </div>`
@@ -132,6 +136,7 @@ export function themeSettingsMarkup() {
132
136
  <button type="button" data-prts-settings-jump="interaction" role="tab" id="prts-tab-interaction" aria-controls="prts-page-interaction" aria-selected="false" tabindex="-1">交互</button>
133
137
  <button type="button" data-prts-settings-jump="flow" role="tab" id="prts-tab-flow" aria-controls="prts-page-flow" aria-selected="false" tabindex="-1">会话流线</button>
134
138
  <button type="button" data-prts-settings-jump="particles" role="tab" id="prts-tab-particles" aria-controls="prts-page-particles" aria-selected="false" tabindex="-1">粒子效果</button>
139
+ <button type="button" data-prts-settings-jump="assistance" role="tab" id="prts-tab-assistance" aria-controls="prts-page-assistance" aria-selected="false" tabindex="-1">辅助功能</button>
135
140
  </nav>
136
141
  <footer data-prts-settings-actions aria-label="全局主题设置操作">
137
142
  <div data-prts-persistence-error hidden role="alert">
@@ -142,7 +147,7 @@ export function themeSettingsMarkup() {
142
147
 
143
148
  <button type="button" data-prts-reset-visual title="恢复全部外观设置" aria-label="恢复全部外观设置">恢复默认</button>
144
149
  <div data-prts-reset-confirm hidden role="group" aria-label="确认恢复全部外观设置">
145
- <span>重置四个分类的外观参数,保留主题开关、启动动画和徽记配置。</span>
150
+ <span>重置全部分类的主题设置,保留主题开关、启动动画和徽记配置。</span>
146
151
  <button type="button" data-prts-reset-confirm-action>确认恢复</button>
147
152
  <button type="button" data-prts-reset-cancel>取消</button>
148
153
  </div>
@@ -180,6 +185,34 @@ export function themeSettingsMarkup() {
180
185
  ${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
181
186
  </div>
182
187
  </section>
188
+ <section data-prts-settings-section="assistance" role="tabpanel" id="prts-page-assistance" aria-labelledby="prts-tab-assistance" tabindex="0" hidden>
189
+ <header data-prts-section-heading><strong>辅助功能</strong><small>会话状态查看与快捷操作</small></header>
190
+ <section data-prts-preview-settings>
191
+ <div data-prts-preview-master>
192
+ ${previewSetting('sessionPreview', '会话状态预览', '在工作区右侧查看会话进度,无需切换当前会话')}
193
+ </div>
194
+ <div data-prts-preview-options>
195
+ <section data-prts-preview-setting-group>
196
+ <header><strong>悬停预览</strong><small>选择哪些状态显示详细浮窗</small></header>
197
+ ${previewSetting('previewOngoing', '运行中预览', '逐轮查看已完成的回复,最新内容置顶')}
198
+ <div data-prts-preview-details="previewOngoing">
199
+ <label data-prts-setting-row="previewReplyLimit"><span><strong>回复条数</strong><small data-prts-preview-setting-description>仅保留最新回复</small></span><select data-prts-preview-reply-limit aria-label="最多显示回复"><option value="5">最新 5 条</option><option value="10">最新 10 条</option><option value="0">不限制</option></select></label>
200
+ ${previewSetting('previewFollowOngoing', '运行结束后跟随', '转为待处理或已完成时,自动切换预览')}
201
+ </div>
202
+ ${previewSetting('previewWarning', '待处理预览(回答与审批)', '查看问题或审批请求,并直接提交选择')}
203
+ <div data-prts-preview-details="previewWarning">
204
+ ${previewSetting('previewFollowWarning', '待处理结束后跟随', '回答或审批后继续跟随;关闭则留在待处理列表')}
205
+ </div>
206
+ ${previewSetting('previewDone', '已完成预览', '查看本轮最终回复')}
207
+
208
+ </section>
209
+ <section data-prts-preview-setting-group>
210
+ <header><strong>快捷操作</strong></header>
211
+ ${previewSetting('previewDoubleClick', '双击浮窗进入会话', '双击正文或空白区域,打开对应会话')}
212
+ </section>
213
+ </div>
214
+ </section>
215
+ </section>
183
216
  <section data-prts-settings-common data-prts-settings-section="flow" role="tabpanel" id="prts-page-flow" aria-labelledby="prts-tab-flow" tabindex="0" hidden>
184
217
  <header data-prts-section-heading><strong>会话流线</strong><small>当前会话的流线与辉光</small></header>
185
218
  <div data-prts-settings-common-grid>
@@ -490,6 +523,13 @@ export function createThemeSettingsOverlay({
490
523
  toggle.setAttribute('aria-checked', String(enabled))
491
524
  toggle.classList.toggle('is-active', enabled)
492
525
  }
526
+ const replyLimitSelect = panel.querySelector('[data-prts-preview-reply-limit]')
527
+ if (replyLimitSelect) replyLimitSelect.value = String(currentPreferences.previewReplyLimit)
528
+ const replyLimitRow = panel.querySelector('[data-prts-setting-row="previewReplyLimit"]')
529
+ if (replyLimitRow) replyLimitRow.hidden = currentPreferences.previewOngoing === false
530
+ for (const details of panel.querySelectorAll('[data-prts-preview-details]')) details.hidden = currentPreferences[details.dataset.prtsPreviewDetails] === false
531
+ const previewOptions = panel.querySelector('[data-prts-preview-options]')
532
+ if (previewOptions) previewOptions.hidden = currentPreferences.sessionPreview === false
493
533
  for (const key of ['sessionGlow', 'sessionFlowPalette', 'sessionFlowColors', 'sessionFlowSpeed']) {
494
534
  const row = panel.querySelector(`[data-prts-setting-row="${key}"]`)
495
535
  if (row) row.hidden = currentPreferences.sessionFlow === false
@@ -609,6 +649,7 @@ export function createThemeSettingsOverlay({
609
649
 
610
650
  function onPanelInput(event) {
611
651
  const target = event.target
652
+ if (target?.matches?.('[data-prts-preview-reply-limit]')) { onPreferenceChange('previewReplyLimit', Number(target.value)); return }
612
653
  if (target?.matches?.('input[data-prts-setting-color]')) {
613
654
  const key = target.dataset.prtsSettingKey
614
655
  const value = normalizeSessionFlowColor(target.value, currentSessionFlowColor(key))
@@ -187,7 +187,7 @@ html[data-dsh-prts] [data-prts-session-summary-popover] {
187
187
  opacity: 0;
188
188
  visibility: hidden;
189
189
  transform: translate3d(0, -4px, 0);
190
- transition: opacity 120ms ease, transform 160ms var(--prts-ease-snap), visibility 0s linear 160ms;
190
+ transition: opacity 70ms ease, transform 90ms var(--prts-ease-snap), visibility 0s linear 90ms;
191
191
  pointer-events: none;
192
192
  }
193
193
 
@@ -201,7 +201,7 @@ html[data-dsh-prts] [data-prts-session-summary-popover]::before {
201
201
  height: 16px;
202
202
  }
203
203
 
204
- html[data-dsh-prts] [data-prts-session-summary-group]:is(:hover, :has(:focus-visible)) [data-prts-session-summary-popover] {
204
+ html[data-dsh-prts] [data-prts-session-summary-group]:is(:hover, :has(:focus-visible), [data-prts-preview-open]) [data-prts-session-summary-popover] {
205
205
  opacity: 1;
206
206
  visibility: visible;
207
207
  transform: translate3d(0, 0, 0);
@@ -271,7 +271,7 @@ html[data-dsh-prts] [data-prts-session-summary-item-status] {
271
271
  font: 700 9px/1 var(--prts-mono);
272
272
  }
273
273
 
274
- html[data-dsh-prts] [data-prts-session-summary-item]:is(:hover, :focus-visible) {
274
+ html[data-dsh-prts] [data-prts-session-summary-item]:is(:hover, :focus-visible, [data-prts-preview-active]) {
275
275
  background: color-mix(in srgb, currentColor 9%, transparent);
276
276
  }
277
277
 
@@ -687,3 +687,96 @@ html[data-dsh-prts] :is(
687
687
  --prts-facility-outline: var(--prts-yellow);
688
688
  background: transparent !important;
689
689
  }
690
+
691
+ html[data-dsh-prts] [data-prts-summary-title] { flex:1; }
692
+ html[data-dsh-prts] [data-prts-summary-title] > span { display:block;width:max-content;min-width:100%; }
693
+ html[data-dsh-prts] [data-prts-summary-reply] {
694
+ position:fixed;
695
+ inset:auto;
696
+ width:296px;
697
+ color:var(--prts-ink);
698
+ box-sizing:border-box;
699
+ margin:0;
700
+ padding:12px 14px;
701
+ border:1px solid var(--prts-line);
702
+ border-radius:8px;
703
+ background:color-mix(in srgb, var(--prts-panel) 85%, transparent);
704
+ box-shadow:var(--prts-shadow-glass-overlay);
705
+ }
706
+ html[data-dsh-prts] [data-prts-summary-reply]::before {
707
+ content:"";
708
+ position:absolute;
709
+ top:0;
710
+ bottom:0;
711
+ right:100%;
712
+ width:12px;
713
+ }
714
+ html[data-dsh-prts] [data-prts-summary-reply][hidden] { display:none; }
715
+ html[data-dsh-prts] [data-prts-summary-reply] strong { display:block;color:var(--prts-muted);font:600 10px/1.5 var(--prts-body);letter-spacing:.04em;padding-bottom:8px;border-bottom:1px solid var(--prts-line); }
716
+ html[data-dsh-prts] [data-prts-summary-reply] [data-prts-preview-body] { margin:9px 0 0;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;max-height:6.6em;overflow:hidden;white-space:normal;overflow-wrap:anywhere;font:12px/1.65 var(--prts-body); }
717
+ html[data-dsh-prts] [data-prts-summary-list] { display:block;max-height:min(32dvh,260px);overflow-y:auto; }
718
+
719
+ html[data-dsh-prts] [data-prts-summary-reply][data-prts-preview-question] > strong { color:var(--prts-yellow); }
720
+ html[data-dsh-prts] [data-prts-summary-reply][data-prts-preview-question] > [data-prts-preview-body] { -webkit-line-clamp:2;max-height:3.3em;font-weight:500; }
721
+ html[data-dsh-prts] [data-prts-preview-choices] { display:flex;flex-wrap:wrap;gap:5px;margin-top:10px; }
722
+ html[data-dsh-prts] [data-prts-preview-choices][hidden] { display:none; }
723
+ html[data-dsh-prts] [data-prts-preview-choices] > span {
724
+ max-width:100%;box-sizing:border-box;padding:3px 7px;border-radius:4px;
725
+ background:color-mix(in srgb,var(--prts-ink) 7%,transparent);
726
+ color:var(--prts-muted);font:11px/1.5 var(--prts-body);
727
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
728
+ }
729
+
730
+ html[data-dsh-prts] [data-prts-preview-actions] { display:flex;gap:6px;flex-wrap:wrap;margin-top:10px; }
731
+ html[data-dsh-prts] [data-prts-preview-actions]:empty { display:none; }
732
+ html[data-dsh-prts] [data-prts-preview-actions] button,
733
+ html[data-dsh-prts] [data-prts-preview-choices] button {
734
+ font:11px/1.5 var(--prts-body);color:var(--prts-ink);
735
+ background:color-mix(in srgb,var(--prts-ink) 6%,transparent);
736
+ border:1px solid var(--prts-line);border-radius:5px;padding:5px 9px;cursor:pointer;
737
+ }
738
+ html[data-dsh-prts] [data-prts-preview-actions] button:hover,
739
+ html[data-dsh-prts] [data-prts-preview-choices] button[aria-pressed="true"] { border-color:var(--prts-yellow);background:color-mix(in srgb,var(--prts-yellow) 12%,transparent); }
740
+ html[data-dsh-prts] [data-prts-preview-actions] button:disabled { opacity:.45;cursor:default; }
741
+ html[data-dsh-prts] [data-prts-preview-editing] { max-height:calc(100dvh - 16px);overflow:auto;background:color-mix(in srgb,var(--prts-panel) 95%,transparent); }
742
+ html[data-dsh-prts] [data-prts-preview-editing] > [data-prts-preview-body] { display:block;max-height:none; }
743
+ html[data-dsh-prts] [data-prts-preview-editing] [data-prts-preview-choices] { flex-direction:column; }
744
+ html[data-dsh-prts] [data-prts-preview-choices] button { text-align:left;white-space:normal;overflow-wrap:anywhere; }
745
+ html[data-dsh-prts] [data-prts-preview-actions] small { width:100%;color:var(--prts-muted);font:11px/1.5 var(--prts-body); }
746
+ html[data-dsh-prts] [data-prts-preview-choices] button small { display:block;margin-top:3px;color:var(--prts-muted);font:10px/1.5 var(--prts-body); }
747
+ html[data-dsh-prts] [data-prts-preview-state="ongoing"] > strong { color:var(--dsw-static-deepseek-450,var(--prts-cyan)); }
748
+ html[data-dsh-prts] [data-prts-preview-state="done"] > strong { color:var(--dsw-alias-state-success-primary,var(--prts-green)); }
749
+ html[data-dsh-prts] [data-prts-preview-state]:not([data-prts-preview-question]) > strong::before {
750
+ content:"";display:inline-block;width:5px;height:5px;margin-right:7px;background:currentColor;border-radius:50%;vertical-align:1px;
751
+ }
752
+ html[data-dsh-prts] [data-prts-preview-empty] > [data-prts-preview-body] { color:var(--prts-muted); }
753
+
754
+ html[data-dsh-prts] [data-prts-summary-reply]:not([data-prts-preview-question]) > [data-prts-preview-body] {
755
+ display:block;max-height:160px;white-space:normal;overflow:auto;overscroll-behavior:contain;
756
+ scrollbar-width:thin;scrollbar-color:var(--prts-muted) transparent;padding-right:5px;
757
+ }
758
+ html[data-dsh-prts] [data-prts-preview-body] :is(p,ul,ol,blockquote,pre,table,h1,h2,h3,h4,h5,h6) { margin:0 0 8px; }
759
+ html[data-dsh-prts] [data-prts-preview-body] > :last-child { margin-bottom:0; }
760
+ html[data-dsh-prts] [data-prts-preview-body] :is(h1,h2,h3,h4,h5,h6) { font:600 12px/1.6 var(--prts-body);color:var(--prts-ink); }
761
+ html[data-dsh-prts] [data-prts-preview-body] strong { display:inline;font:inherit;font-weight:600;color:inherit;letter-spacing:normal;padding:0;border:0; }
762
+ html[data-dsh-prts] [data-prts-preview-body] :is(ul,ol) { padding-left:18px; }
763
+ html[data-dsh-prts] [data-prts-preview-body] blockquote { padding-left:9px;border-left:2px solid var(--prts-line);color:var(--prts-muted); }
764
+ html[data-dsh-prts] [data-prts-preview-body] table { border-collapse:collapse;font-size:11px;min-width:100%; }
765
+ html[data-dsh-prts] [data-prts-preview-body] :is(th,td) { border:1px solid var(--prts-line);padding:4px 7px;text-align:left;min-width:64px; }
766
+ html[data-dsh-prts] [data-prts-preview-body] code { font:11px/1.6 var(--prts-mono);background:color-mix(in srgb,var(--prts-ink) 6%,transparent);border-radius:3px;padding:1px 3px; }
767
+ html[data-dsh-prts] [data-prts-preview-body] pre { padding:7px;background:color-mix(in srgb,var(--prts-ink) 5%,transparent);border-radius:4px;white-space:pre; }
768
+ html[data-dsh-prts] [data-prts-preview-body] pre code { background:none;padding:0; }
769
+ html[data-dsh-prts] [data-prts-preview-body] a { color:var(--prts-cyan);text-decoration:underline; }
770
+ html[data-dsh-prts] [data-prts-preview-message] + [data-prts-preview-message] { margin-top:5px; }
771
+ html[data-dsh-prts] [data-prts-preview-message] > small { display:block;margin-bottom:6px;color:var(--prts-muted);font:10px/1.5 var(--prts-mono); }
772
+
773
+ html[data-dsh-prts] [data-prts-reply-entry] { display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;text-align:left;border:1px solid var(--prts-line);border-radius:5px;background:transparent;color:var(--prts-ink);cursor:pointer; }
774
+ html[data-dsh-prts] [data-prts-reply-entry]:is(:hover,:focus-visible,[data-prts-reply-active]) { background:color-mix(in srgb,var(--prts-ink) 7%,transparent);border-color:var(--prts-muted); }
775
+ html[data-dsh-prts] [data-prts-reply-entry] small { flex:0 0 auto;white-space:nowrap;padding-top:2px;color:var(--prts-muted);font:10px/1.5 var(--prts-mono); }
776
+ html[data-dsh-prts] [data-prts-reply-entry] span { min-width:0;flex:1;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;font:12px/1.6 var(--prts-body);overflow-wrap:anywhere; }
777
+ html[data-dsh-prts] [data-prts-reply-detail]:not([data-prts-preview-question]) > [data-prts-preview-body] { max-height:190px; }
778
+
779
+ html[data-dsh-prts] [data-prts-preview-marker] { display:none;flex:none;margin-left:auto;color:var(--prts-muted);font:600 9px/1.4 var(--prts-body); }
780
+ html[data-dsh-prts] [data-prts-preview-active] > [data-prts-preview-marker] { display:block; }
781
+ html[data-dsh-prts] [data-prts-session-summary-item][data-prts-preview-active] { box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--prts-ink) 30%,transparent);border-top-color:transparent;border-radius:5px;background:color-mix(in srgb,currentColor 9%,transparent); }
782
+ html[data-dsh-prts] [data-prts-preview-session-title] { overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:5px;color:var(--prts-muted);font:400 11px/1.5 var(--prts-body); }
@@ -904,12 +904,6 @@ html[data-dsh-prts] [data-prts-session-row]::before {
904
904
  inset: 0 auto 0 0;
905
905
  width: var(--prts-spine-width);
906
906
  background: var(--prts-line-strong);
907
- clip-path: polygon(
908
- 3px 0, calc(100% - 3px) 0, 100% 3px,
909
- 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px),
910
- 100% calc(100% - 3px), calc(100% - 3px) 100%, 3px 100%, 0 calc(100% - 3px),
911
- 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 3px
912
- );
913
907
  transform: scaleX(var(--prts-spine-rest));
914
908
  transform-origin: left center;
915
909
  transition: transform 220ms var(--prts-ease-spring), background-color 160ms ease;
@@ -935,9 +929,9 @@ html[data-dsh-prts] [data-prts-facility-face] {
935
929
  3px 0,
936
930
  calc(50% - 25px) 0, calc(50% - 20px) 3.333px, calc(50% + 20px) 3.333px, calc(50% + 25px) 0,
937
931
  calc(100% - 3px) 0, 100% 3px,
938
- 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px),
932
+ 100% calc(50% - 5px), calc(100% - .824px) calc(50% - 3px), calc(100% - .824px) calc(50% + 3px), 100% calc(50% + 5px),
939
933
  100% calc(100% - 3px), calc(100% - 3px) 100%, 3px 100%, 0 calc(100% - 3px),
940
- 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 3px
934
+ 0 calc(50% + 5px), .824px calc(50% + 3px), .824px calc(50% - 3px), 0 calc(50% - 5px), 0 3px
941
935
  );
942
936
  transform: translate3d(var(--prts-row-shift), 0, 0);
943
937
  transition: transform 220ms var(--prts-ease-spring), border-color 160ms ease, background-color 160ms ease;
@@ -1036,7 +1030,7 @@ html[data-dsh-prts] [data-prts-session-actions] button {
1036
1030
  border-radius: 0 !important;
1037
1031
  background: var(--prts-panel-raised) !important;
1038
1032
  color: var(--prts-muted) !important;
1039
- clip-path: polygon(3px 0, calc(100% - 3px) 0, 100% 3px, 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px), 100% calc(100% - 3px), calc(100% - 3px) 100%, 3px 100%, 0 calc(100% - 3px), 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 3px);
1033
+ clip-path: polygon(3px 0, calc(100% - 3px) 0, 100% 3px, 100% calc(50% - 5px), calc(100% - .824px) calc(50% - 3px), calc(100% - .824px) calc(50% + 3px), 100% calc(50% + 5px), 100% calc(100% - 3px), calc(100% - 3px) 100%, 3px 100%, 0 calc(100% - 3px), 0 calc(50% + 5px), .824px calc(50% + 3px), .824px calc(50% - 3px), 0 calc(50% - 5px), 0 3px);
1040
1034
  cursor: pointer;
1041
1035
  }
1042
1036
 
@@ -55,9 +55,9 @@ html[data-dsh-prts] [data-prts-workspace-row]::before,
55
55
  html[data-dsh-prts] [data-prts-session-row]::before {
56
56
  clip-path: polygon(
57
57
  1px 0, calc(100% - 1px) 0, 100% 1px,
58
- 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px),
58
+ 100% calc(50% - 5px), 88.235% calc(50% - 3px), 88.235% calc(50% + 3px), 100% calc(50% + 5px),
59
59
  100% calc(100% - 1px), calc(100% - 1px) 100%, 1px 100%, 0 calc(100% - 1px),
60
- 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 1px
60
+ 0 calc(50% + 5px), 11.765% calc(50% + 3px), 11.765% calc(50% - 3px), 0 calc(50% - 5px), 0 1px
61
61
  ) !important;
62
62
  }
63
63
 
@@ -72,9 +72,9 @@ html[data-dsh-prts] [data-prts-facility-face] {
72
72
  1px 0,
73
73
  calc(50% - 25px) 0, calc(50% - 20px) 3.333px, calc(50% + 20px) 3.333px, calc(50% + 25px) 0,
74
74
  calc(100% - 1px) 0, 100% 1px,
75
- 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px),
75
+ 100% calc(50% - 5px), calc(100% - .824px) calc(50% - 3px), calc(100% - .824px) calc(50% + 3px), 100% calc(50% + 5px),
76
76
  100% calc(100% - 1px), calc(100% - 1px) 100%, 1px 100%, 0 calc(100% - 1px),
77
- 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 1px
77
+ 0 calc(50% + 5px), .824px calc(50% + 3px), .824px calc(50% - 3px), 0 calc(50% - 5px), 0 1px
78
78
  ) !important;
79
79
  }
80
80
 
@@ -87,9 +87,9 @@ html[data-dsh-prts] [data-prts-facility-face]::after {
87
87
  0 0,
88
88
  calc(50% - 24px) 0, calc(50% - 19px) 3.333px, calc(50% + 19px) 3.333px, calc(50% + 24px) 0,
89
89
  100% 0,
90
- 100% calc(50% - 4px), calc(100% - 2px) calc(50% - 2px), calc(100% - 2px) calc(50% + 2px), 100% calc(50% + 4px),
90
+ 100% calc(50% - 4px), calc(100% - .824px) calc(50% - 2px), calc(100% - .824px) calc(50% + 2px), 100% calc(50% + 4px),
91
91
  100% 100%, 0 100%,
92
- 0 calc(50% + 4px), 2px calc(50% + 2px), 2px calc(50% - 2px), 0 calc(50% - 4px)
92
+ 0 calc(50% + 4px), .824px calc(50% + 2px), .824px calc(50% - 2px), 0 calc(50% - 4px)
93
93
  );
94
94
  }
95
95
 
@@ -113,9 +113,9 @@ html[data-dsh-prts] [data-prts-session-actions] button {
113
113
  background: var(--prts-line) !important;
114
114
  clip-path: polygon(
115
115
  1px 0, calc(100% - 1px) 0, 100% 1px,
116
- 100% calc(50% - 5px), calc(100% - 2px) calc(50% - 3px), calc(100% - 2px) calc(50% + 3px), 100% calc(50% + 5px),
116
+ 100% calc(50% - 5px), calc(100% - .824px) calc(50% - 3px), calc(100% - .824px) calc(50% + 3px), 100% calc(50% + 5px),
117
117
  100% calc(100% - 1px), calc(100% - 1px) 100%, 1px 100%, 0 calc(100% - 1px),
118
- 0 calc(50% + 5px), 2px calc(50% + 3px), 2px calc(50% - 3px), 0 calc(50% - 5px), 0 1px
118
+ 0 calc(50% + 5px), .824px calc(50% + 3px), .824px calc(50% - 3px), 0 calc(50% - 5px), 0 1px
119
119
  ) !important;
120
120
  }
121
121
 
@@ -128,9 +128,9 @@ html[data-dsh-prts] [data-prts-session-actions] button::before {
128
128
  background: var(--prts-panel-raised);
129
129
  clip-path: polygon(
130
130
  0 0, 100% 0,
131
- 100% calc(50% - 4px), calc(100% - 2px) calc(50% - 2px), calc(100% - 2px) calc(50% + 2px), 100% calc(50% + 4px),
131
+ 100% calc(50% - 4px), calc(100% - .824px) calc(50% - 2px), calc(100% - .824px) calc(50% + 2px), 100% calc(50% + 4px),
132
132
  100% 100%, 0 100%,
133
- 0 calc(50% + 4px), 2px calc(50% + 2px), 2px calc(50% - 2px), 0 calc(50% - 4px)
133
+ 0 calc(50% + 4px), .824px calc(50% + 2px), .824px calc(50% - 2px), 0 calc(50% - 4px)
134
134
  );
135
135
  pointer-events: none;
136
136
  }
@@ -915,3 +915,30 @@ html[data-dsh-prts]{
915
915
  [data-prts-avatar-crop] [data-prts-avatar-upload]{padding:6px 10px;font-size:12px}
916
916
  }
917
917
  @media(max-width:480px){html[data-dsh-prts] [data-prts-conversation-cards]{grid-template-columns:1fr}}
918
+ html[data-dsh-prts] [data-prts-preview-settings] {
919
+ & { border:1px solid var(--prts-line);border-radius:8px;overflow:hidden;background:var(--prts-panel); }
920
+ & [data-prts-setting-row] { display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border:0;background:none; }
921
+ & [data-prts-setting-row] > span { display:grid;gap:5px;min-width:0; }
922
+ & [data-prts-setting-row] strong { font:600 12px/1.5 var(--prts-body); }
923
+ & [data-prts-setting-row="previewReplyLimit"] { padding:0 0 14px;gap:12px; }
924
+ & [data-prts-setting-row="previewReplyLimit"] strong { color:var(--prts-muted);font-size:11px; }
925
+ & [data-prts-preview-reply-limit] { min-width:112px;padding:7px 10px;border:1px solid var(--prts-line);border-radius:5px;background:var(--prts-panel-raised);color:var(--prts-ink);font:400 11px/1.5 var(--prts-body); }
926
+ & [data-prts-preview-reply-limit]:focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
927
+ & [data-prts-preview-details] { padding:0 12px;margin:0 -12px 8px 0;border-radius:5px;background:color-mix(in srgb,var(--prts-ink) 3%,transparent); }
928
+ & [data-prts-preview-details] [data-prts-setting-row] { padding:10px 0; }
929
+ & [data-prts-preview-details] [data-prts-setting-row] strong { font-size:11px; }
930
+ & [data-prts-preview-setting-description] { color:var(--prts-muted);font:400 11px/1.6 var(--prts-body); }
931
+ & [data-prts-preview-master] { padding:8px 20px;background:color-mix(in srgb,var(--prts-yellow) 5%,transparent); }
932
+ & [data-prts-preview-master] [data-prts-setting-row] strong { font-size:14px; }
933
+ & [data-prts-preview-options] { padding:0 20px 6px;border-top:1px solid var(--prts-line); }
934
+ & [data-prts-preview-setting-group] { padding:18px 0 4px; }
935
+ & [data-prts-preview-setting-group] + [data-prts-preview-setting-group] { border-top:1px solid var(--prts-line); }
936
+ & [data-prts-preview-setting-group] > header { display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px;margin-bottom:3px; }
937
+ & [data-prts-preview-setting-group] > header strong { color:var(--prts-muted);font:600 11px/1.5 var(--prts-body); }
938
+ & [data-prts-preview-setting-group] > header small { color:var(--prts-muted);font:400 10px/1.5 var(--prts-body); }
939
+ @media(max-width:640px) {
940
+ & [data-prts-preview-master] { padding-inline:14px; }
941
+ & [data-prts-preview-options] { padding-inline:14px; }
942
+ & [data-prts-setting-row] { gap:12px; }
943
+ }
944
+ }