@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ash-qw/dsh-theme-prts",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
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"
@@ -1,7 +1,7 @@
1
1
  export const FACILITY_GEOMETRY = Object.freeze({
2
2
  stroke: 1,
3
3
  corner: 1,
4
- sideDepth: 2,
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 - FACILITY_GEOMETRY.sideOpening / 2),
51
- point(right - FACILITY_GEOMETRY.sideDepth, centerY - FACILITY_GEOMETRY.sideInner / 2),
52
- point(right - FACILITY_GEOMETRY.sideDepth, centerY + FACILITY_GEOMETRY.sideInner / 2),
53
- point(right, centerY + FACILITY_GEOMETRY.sideOpening / 2),
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 + FACILITY_GEOMETRY.sideOpening / 2),
59
- point(left + FACILITY_GEOMETRY.sideDepth, centerY + FACILITY_GEOMETRY.sideInner / 2),
60
- point(left + FACILITY_GEOMETRY.sideDepth, centerY - FACILITY_GEOMETRY.sideInner / 2),
61
- point(left, centerY - FACILITY_GEOMETRY.sideOpening / 2),
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 }))
@@ -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 })
@@ -257,7 +259,7 @@ export function applyPrtsPlugin(ctx, environment) {
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
 
@@ -30,6 +30,15 @@ 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
+ previewFollowOngoing: true,
38
+ previewFollowWarning: false,
39
+ previewDoubleClick: true,
40
+ previewReplyLimit: 5,
41
+
33
42
  sessionFlow: true,
34
43
  sessionGlow: true,
35
44
  sessionFlowPalette: 'triad',
@@ -80,7 +89,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
80
89
  particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
81
90
  material: Object.freeze(['glass']),
82
91
  accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
83
- navigation: Object.freeze(['railDefaultHidden']),
92
+ navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
84
93
  conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'conversationEmblem']),
85
94
  })
86
95
 
@@ -197,6 +206,14 @@ export function normalizePreferences(value) {
197
206
  sessionFlowColorCore: sessionFlowColors.core,
198
207
  sessionFlowColorFront: sessionFlowColors.front,
199
208
  sessionFlowSpeed: sessionFlowSpeedOr(input.sessionFlowSpeed, DEFAULT_PREFERENCES.sessionFlowSpeed),
209
+ sessionPreview: booleanOr(input.sessionPreview, DEFAULT_PREFERENCES.sessionPreview),
210
+ previewOngoing: booleanOr(input.previewOngoing, DEFAULT_PREFERENCES.previewOngoing),
211
+ previewWarning: booleanOr(input.previewWarning, DEFAULT_PREFERENCES.previewWarning),
212
+ previewDone: booleanOr(input.previewDone, DEFAULT_PREFERENCES.previewDone),
213
+ previewReplyLimit: [0, 5, 10, '0', '5', '10'].includes(input.previewReplyLimit) ? Number(input.previewReplyLimit) : 5,
214
+ previewFollowOngoing: booleanOr(input.previewFollowOngoing, booleanOr(input.previewFollowState, true)),
215
+ previewFollowWarning: booleanOr(input.previewFollowWarning, booleanOr(input.previewFollowState, false)),
216
+ previewDoubleClick: booleanOr(input.previewDoubleClick, DEFAULT_PREFERENCES.previewDoubleClick),
200
217
  conversationEmblem: booleanOr(input.conversationEmblem, DEFAULT_PREFERENCES.conversationEmblem),
201
218
  bakerGroupAvatars: booleanOr(input.bakerGroupAvatars, DEFAULT_PREFERENCES.bakerGroupAvatars),
202
219
  conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
@@ -0,0 +1,44 @@
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
+ }
19
+
20
+ // Bounded per retained session; oversized replies are rendered without caching.
21
+ export function createPreviewContentCache(document, render = renderPreviewMarkdown) {
22
+ const entries = new Map()
23
+ let size = 0
24
+ return text => {
25
+ const hit = entries.get(text)
26
+ if (hit) { entries.delete(text); entries.set(text, hit); return hit }
27
+ const html = render(text)
28
+ const plain = document.createElement('div')
29
+ plain.innerHTML = html
30
+ const summary = plain.textContent.replace(/\s+/g, ' ').trim().slice(0, 100)
31
+ const cost = text.length + html.length + summary.length
32
+ const value = {html, summary, cost}
33
+ if (cost <= 262144) {
34
+ while (entries.size && (entries.size >= 32 || size + cost > 262144)) {
35
+ const key = entries.keys().next().value
36
+ size -= entries.get(key).cost
37
+ entries.delete(key)
38
+ }
39
+ entries.set(text, value)
40
+ size += cost
41
+ }
42
+ return value
43
+ }
44
+ }