@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/CHANGELOG.md +21 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/THIRD_PARTY_NOTICES.zh-CN.md +7 -0
- package/lib/client.js +1010 -31
- package/package.json +3 -2
- package/src/client/facility-geometry.js +14 -10
- package/src/client/facility-vector.js +1 -1
- package/src/client/index.js +3 -1
- package/src/client/preferences.js +18 -1
- package/src/client/preview-markdown.js +44 -0
- package/src/client/session-summary-preview.js +599 -0
- package/src/client/sidebar-control-adapter.js +149 -14
- package/src/client/theme-settings-workbench.js +43 -2
- package/src/styles/facility-vector.css +96 -3
- package/src/styles/prts.css +3 -9
- package/src/styles/refinement.css +10 -10
- package/src/styles/theme-settings-workbench.css +27 -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.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:
|
|
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 })
|
|
@@ -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
|
+
}
|