dsh-plugin-xujie-memory 0.6.0 → 0.7.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/ACCEPTANCE.md +37 -0
- package/BUILD-PROVENANCE.json +53 -0
- package/CHANGELOG.md +12 -0
- package/README.md +48 -23
- package/README_EN.md +32 -9
- package/lib/client.js +17219 -0
- package/lib/controller.js +307 -0
- package/lib/index.js +87 -22
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +15104 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +15093 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +49 -5
- package/python/xujie_memory/__init__.py +123 -123
- package/python/xujie_memory/engine.py +1333 -1312
- package/python/xujie_memory/management.py +203 -0
- package/python/xujie_memory/memory.py +27 -21
- package/python/xujie_memory/privacy.py +105 -0
- package/python/xujie_memory/schema.sql +492 -480
- package/python/xujie_memory/server.py +52 -0
- package/scripts/build.mjs +19 -0
- package/scripts/prepack.mjs +9 -0
- package/scripts/test-all.mjs +9 -0
- package/scripts/test-node.mjs +10 -0
- package/src/client/MemoryPanel.tsx +536 -0
- package/src/client/NeuralField.tsx +88 -0
- package/src/client/css.d.ts +4 -0
- package/src/client/icons.tsx +28 -0
- package/src/client/index.tsx +59 -0
- package/src/client/memory-adapter.ts +67 -0
- package/src/client/model.ts +79 -0
- package/src/client/neural/engine.ts +999 -0
- package/src/client/neural/morphology.ts +219 -0
- package/src/client/panel.css +337 -0
- package/src/client/types.ts +6 -0
- package/src/controller.ts +88 -0
- package/src/generated/typert.host.js +449 -0
- package/src/generated/typert.remote-client.js +438 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/test/cordis-fixture.mjs +5 -0
- package/test/management_regression.py +187 -0
- package/test/node-regression.entry.mjs +52 -0
- package/vendor/DSH-LICENSE.txt +21 -0
- package/vendor/THIRD-PARTY.md +6 -0
- package/vendor/dsh-protocol/index.ts +484 -0
- package/vendor/dsh-protocol/json-value.ts +53 -0
- package/vendor/dsh-protocol/owned-value.ts +39 -0
- package/vendor/dsh-protocol/principal.ts +17 -0
- package/vendor/dsh-protocol/remote-access.ts +38 -0
- package/vendor/dsh-protocol/remote-error.ts +49 -0
- package/vendor/dsh-protocol/remote-info.ts +161 -0
- package/vendor/dsh-protocol/types.ts +939 -0
- package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
- package/vendor/licenses/zod-MIT.txt +21 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedural 3D morphology of one pyramidal neuron spanning three memory strata.
|
|
3
|
+
*
|
|
4
|
+
* The fibres are structure only: they never represent a memory record. Real
|
|
5
|
+
* records are placed later as synaptic boutons on vertices of the stratum that
|
|
6
|
+
* matches their layer, and real relations are drawn as separate connections.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type LayerIndex = 0 | 1 | 2
|
|
10
|
+
/** 0 semantic (apical tuft), 1 episodic (equatorial), 2 working (basal), 3 neutral trunk. */
|
|
11
|
+
export type FibreGroup = LayerIndex | 3
|
|
12
|
+
|
|
13
|
+
export interface Stratum { y: number; radius: number; half: number }
|
|
14
|
+
|
|
15
|
+
/** World-space strata. Screen-up is negative y. */
|
|
16
|
+
export const STRATA: readonly Stratum[] = [
|
|
17
|
+
{ y: -262, radius: 340, half: 48 },
|
|
18
|
+
{ y: 0, radius: 390, half: 44 },
|
|
19
|
+
{ y: 258, radius: 330, half: 48 },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export const SOMA_RADIUS = 40
|
|
23
|
+
|
|
24
|
+
export interface Morphology {
|
|
25
|
+
count: number
|
|
26
|
+
x: Float32Array
|
|
27
|
+
y: Float32Array
|
|
28
|
+
z: Float32Array
|
|
29
|
+
/** Fibre radius at the vertex in world units. */
|
|
30
|
+
r: Float32Array
|
|
31
|
+
parent: Int32Array
|
|
32
|
+
group: Uint8Array
|
|
33
|
+
/** 0 dendrite, 1 spine, 2 axon. */
|
|
34
|
+
kind: Uint8Array
|
|
35
|
+
segA: Int32Array
|
|
36
|
+
segB: Int32Array
|
|
37
|
+
segCount: number
|
|
38
|
+
/** Vertex indices suitable for synaptic boutons, per stratum. */
|
|
39
|
+
sites: [Int32Array, Int32Array, Int32Array]
|
|
40
|
+
/** Diffuse extracellular particles per stratum: xyz + phase. */
|
|
41
|
+
dust: [Float32Array, Float32Array, Float32Array]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function mulberry32(seed: number): () => number {
|
|
45
|
+
let a = seed >>> 0
|
|
46
|
+
return () => {
|
|
47
|
+
a = (a + 0x6d2b79f5) >>> 0
|
|
48
|
+
let t = a
|
|
49
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
50
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
51
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface V { x: number; y: number; z: number }
|
|
56
|
+
const norm = (v: V): V => { const l = Math.hypot(v.x, v.y, v.z) || 1; return { x: v.x / l, y: v.y / l, z: v.z / l } }
|
|
57
|
+
const cross = (a: V, b: V): V => ({ x: a.y * b.z - a.z * b.y, y: a.z * b.x - a.x * b.z, z: a.x * b.y - a.y * b.x })
|
|
58
|
+
|
|
59
|
+
export function buildMorphology(seed = 0x7a11e): Morphology {
|
|
60
|
+
const rnd = mulberry32(seed)
|
|
61
|
+
const gauss = () => (rnd() + rnd() + rnd() - 1.5) / 1.5
|
|
62
|
+
const cap = 40000
|
|
63
|
+
const x = new Float32Array(cap), y = new Float32Array(cap), z = new Float32Array(cap)
|
|
64
|
+
const r = new Float32Array(cap), parent = new Int32Array(cap)
|
|
65
|
+
const group = new Uint8Array(cap), kind = new Uint8Array(cap)
|
|
66
|
+
const segA = new Int32Array(cap), segB = new Int32Array(cap)
|
|
67
|
+
const siteLists: number[][] = [[], [], []]
|
|
68
|
+
const branchDepth = new Uint8Array(cap)
|
|
69
|
+
let n = 0, s = 0
|
|
70
|
+
|
|
71
|
+
const add = (p: V, rad: number, par: number, g: FibreGroup, k: number, depth: number): number => {
|
|
72
|
+
if (n >= cap) return -1
|
|
73
|
+
x[n] = p.x; y[n] = p.y; z[n] = p.z; r[n] = rad; parent[n] = par; kind[n] = k; branchDepth[n] = depth
|
|
74
|
+
// Fibres close to the soma read as one neutral trunk system.
|
|
75
|
+
group[n] = Math.hypot(p.x, p.y, p.z) < SOMA_RADIUS * 2.1 && k !== 1 ? 3 : g
|
|
76
|
+
if (par >= 0 && s < cap) { segA[s] = par; segB[s] = n; s++ }
|
|
77
|
+
return n++
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const perpendicular = (d: V): V => {
|
|
81
|
+
const helper = Math.abs(d.y) < 0.9 ? { x: 0, y: 1, z: 0 } : { x: 1, y: 0, z: 0 }
|
|
82
|
+
const a = norm(cross(d, helper)), b = cross(d, a)
|
|
83
|
+
const t = rnd() * Math.PI * 2
|
|
84
|
+
return norm({ x: a.x * Math.cos(t) + b.x * Math.sin(t), y: a.y * Math.cos(t) + b.y * Math.sin(t), z: a.z * Math.cos(t) + b.z * Math.sin(t) })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface Branch { from: number; pos: V; dir: V; len: number; rad: number; depth: number; g: FibreGroup; target: Stratum | null; maxDepth: number; jitter: number; k: number }
|
|
88
|
+
const stack: Branch[] = []
|
|
89
|
+
|
|
90
|
+
const grow = (b: Branch) => {
|
|
91
|
+
let pos = b.pos, dir = b.dir, rad = b.rad, prev = b.from, travelled = 0
|
|
92
|
+
let sinceBranch = 0
|
|
93
|
+
// Branches born inside their stratum stay confined to it.
|
|
94
|
+
const confined = b.target !== null && Math.abs(b.pos.y - b.target.y) < b.target.half * 1.5
|
|
95
|
+
while (travelled < b.len && rad > 0.16) {
|
|
96
|
+
const step = (b.depth === 0 ? 10 : 8) + rnd() * 4
|
|
97
|
+
dir = norm({ x: dir.x + gauss() * b.jitter, y: dir.y + gauss() * b.jitter, z: dir.z + gauss() * b.jitter })
|
|
98
|
+
if (b.target) {
|
|
99
|
+
const t = b.target
|
|
100
|
+
const dy = (t.y - pos.y) / (t.half * (confined ? 3 : 4.5))
|
|
101
|
+
dir = norm({ x: dir.x, y: dir.y + Math.max(-0.5, Math.min(0.6, dy)) * (confined ? 1.5 : 0.32), z: dir.z })
|
|
102
|
+
const rr = Math.hypot(pos.x, pos.z) || 1
|
|
103
|
+
const inside = Math.abs(pos.y - t.y) < t.half
|
|
104
|
+
if (inside) {
|
|
105
|
+
// Inside the stratum fibres flatten out and spread to fill its disc.
|
|
106
|
+
dir = norm({ x: dir.x + (rr < t.radius * 0.72 ? (pos.x / rr) * 0.2 : 0), y: dir.y * 0.55, z: dir.z + (rr < t.radius * 0.72 ? (pos.z / rr) * 0.2 : 0) })
|
|
107
|
+
}
|
|
108
|
+
if (rr > t.radius * 0.9) {
|
|
109
|
+
const pull = 0.5 * (rr / t.radius - 0.9) * 4
|
|
110
|
+
dir = norm({ x: dir.x - (pos.x / rr) * pull, y: dir.y, z: dir.z - (pos.z / rr) * pull })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
pos = { x: pos.x + dir.x * step, y: pos.y + dir.y * step, z: pos.z + dir.z * step }
|
|
114
|
+
travelled += step; sinceBranch += step
|
|
115
|
+
rad *= b.depth === 0 ? 0.984 : 0.975
|
|
116
|
+
const idx = add(pos, rad, prev, b.g, b.k, b.depth)
|
|
117
|
+
if (idx < 0) return
|
|
118
|
+
prev = idx
|
|
119
|
+
const inStratum = b.target && Math.abs(pos.y - b.target.y) < b.target.half * 1.7
|
|
120
|
+
if (inStratum && b.k === 0 && b.depth >= 1 && rnd() < 0.6) siteLists[b.g as number]?.push(idx)
|
|
121
|
+
// Dendritic spines: short, very fine side processes that give the field its texture.
|
|
122
|
+
if (b.k !== 2 && b.depth >= 1 && rnd() < 0.5) {
|
|
123
|
+
const sp = perpendicular(dir), len = 3 + rnd() * 6
|
|
124
|
+
add({ x: pos.x + sp.x * len, y: pos.y + sp.y * len, z: pos.z + sp.z * len }, 0.22, idx, b.g, 1, b.depth + 1)
|
|
125
|
+
}
|
|
126
|
+
const remaining = b.len - travelled
|
|
127
|
+
if (b.depth < b.maxDepth && sinceBranch > 26 && remaining > 40 && rnd() < 0.17 + b.depth * 0.015) {
|
|
128
|
+
sinceBranch = 0
|
|
129
|
+
const p = perpendicular(dir), ang = 0.45 + rnd() * 0.55
|
|
130
|
+
const cd = norm({ x: dir.x * Math.cos(ang) + p.x * Math.sin(ang), y: dir.y * Math.cos(ang) + p.y * Math.sin(ang), z: dir.z * Math.cos(ang) + p.z * Math.sin(ang) })
|
|
131
|
+
// Bifurcation also deflects the parent slightly away.
|
|
132
|
+
dir = norm({ x: dir.x - p.x * 0.25, y: dir.y - p.y * 0.25, z: dir.z - p.z * 0.25 })
|
|
133
|
+
stack.push({ ...b, from: idx, pos, dir: cd, len: remaining * (0.62 + rnd() * 0.3), rad: Math.max(0.3, rad * 0.74), depth: b.depth + 1, jitter: Math.min(0.42, b.jitter + 0.04) })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (prev >= 0 && b.k === 0 && b.target) siteLists[b.g as number]?.push(prev)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const drain = () => { while (stack.length) grow(stack.pop()!) }
|
|
140
|
+
const somaAt = (d: V): V => ({ x: d.x * SOMA_RADIUS * 0.92, y: d.y * SOMA_RADIUS * 0.92, z: d.z * SOMA_RADIUS * 0.92 })
|
|
141
|
+
|
|
142
|
+
// Apical trunk: a thick, slightly wandering shaft to the semantic tuft.
|
|
143
|
+
{
|
|
144
|
+
let pos: V = somaAt({ x: 0, y: -1, z: 0 })
|
|
145
|
+
let prev = add(pos, 6.2, -1, 3, 0, 0)
|
|
146
|
+
let rad = 6.2
|
|
147
|
+
const top = STRATA[0]!.y + 18
|
|
148
|
+
let dir: V = { x: 0.04, y: -1, z: 0.02 }
|
|
149
|
+
const obliqueAt = new Set([5, 9, 13, 16])
|
|
150
|
+
for (let i = 0; pos.y > top; i++) {
|
|
151
|
+
dir = norm({ x: dir.x + gauss() * 0.05 - pos.x * 0.002, y: -1, z: dir.z + gauss() * 0.05 - pos.z * 0.002 })
|
|
152
|
+
pos = { x: pos.x + dir.x * 11, y: pos.y + dir.y * 11, z: pos.z + dir.z * 11 }
|
|
153
|
+
rad = Math.max(3.2, rad * 0.975)
|
|
154
|
+
prev = add(pos, rad, prev, 0, 0, 0)
|
|
155
|
+
if (obliqueAt.has(i)) {
|
|
156
|
+
const a = rnd() * Math.PI * 2
|
|
157
|
+
stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: -0.55, z: Math.sin(a) }), len: 240 + rnd() * 80, rad: 1.7, depth: 1, g: 0, target: STRATA[0]!, maxDepth: 4, jitter: 0.2, k: 0 })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (let i = 0; i < 8; i++) {
|
|
161
|
+
const a = (i / 8) * Math.PI * 2 + rnd() * 0.5
|
|
162
|
+
stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: -0.18, z: Math.sin(a) }), len: 300 + rnd() * 90, rad: 2.6, depth: 1, g: 0, target: STRATA[0]!, maxDepth: 5, jitter: 0.17, k: 0 })
|
|
163
|
+
}
|
|
164
|
+
drain()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Equatorial dendrites into the episodic stratum.
|
|
168
|
+
for (let i = 0; i < 10; i++) {
|
|
169
|
+
const a = (i / 10) * Math.PI * 2 + rnd() * 0.4
|
|
170
|
+
const d = norm({ x: Math.cos(a), y: gauss() * 0.12, z: Math.sin(a) })
|
|
171
|
+
const start = somaAt(d)
|
|
172
|
+
const root = add(start, 4.2, -1, 3, 0, 0)
|
|
173
|
+
stack.push({ from: root, pos: start, dir: d, len: 400 + rnd() * 70, rad: 3.6, depth: 0, g: 1, target: STRATA[1]!, maxDepth: 5, jitter: 0.15, k: 0 })
|
|
174
|
+
drain()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Basal dendrites descending into the working stratum.
|
|
178
|
+
for (let i = 0; i < 7; i++) {
|
|
179
|
+
const a = (i / 7) * Math.PI * 2 + rnd() * 0.5
|
|
180
|
+
const d = norm({ x: Math.cos(a) * 0.8, y: 0.6, z: Math.sin(a) * 0.8 })
|
|
181
|
+
const start = somaAt(d)
|
|
182
|
+
const root = add(start, 3.8, -1, 3, 0, 0)
|
|
183
|
+
stack.push({ from: root, pos: start, dir: d, len: 440 + rnd() * 70, rad: 3.2, depth: 0, g: 2, target: STRATA[2]!, maxDepth: 5, jitter: 0.15, k: 0 })
|
|
184
|
+
drain()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Axon: a fine straight process with collaterals into the working stratum.
|
|
188
|
+
{
|
|
189
|
+
let pos = somaAt(norm({ x: 0.18, y: 1, z: -0.12 }))
|
|
190
|
+
let prev = add(pos, 1.9, -1, 3, 2, 0)
|
|
191
|
+
let dir = norm({ x: 0.12, y: 1, z: -0.08 })
|
|
192
|
+
for (let i = 0; i < 31; i++) {
|
|
193
|
+
dir = norm({ x: dir.x + gauss() * 0.03, y: 1, z: dir.z + gauss() * 0.03 })
|
|
194
|
+
pos = { x: pos.x + dir.x * 11, y: pos.y + dir.y * 11, z: pos.z + dir.z * 11 }
|
|
195
|
+
prev = add(pos, 1.4, prev, 2, 2, 0)
|
|
196
|
+
if (i === 17 || i === 21 || i === 25 || i === 30) {
|
|
197
|
+
const a = rnd() * Math.PI * 2
|
|
198
|
+
stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: 0.05, z: Math.sin(a) }), len: 200 + rnd() * 80, rad: 1.0, depth: 2, g: 2, target: STRATA[2]!, maxDepth: 4, jitter: 0.2, k: 0 })
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
drain()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sites = siteLists.map(list => Int32Array.from(new Set(list))) as [Int32Array, Int32Array, Int32Array]
|
|
205
|
+
const dust = STRATA.map((st, li) => {
|
|
206
|
+
const count = li === 1 ? 520 : 440
|
|
207
|
+
const out = new Float32Array(count * 4)
|
|
208
|
+
for (let i = 0; i < count; i++) {
|
|
209
|
+
const rr = st.radius * Math.sqrt(rnd()) * 1.08, a = rnd() * Math.PI * 2
|
|
210
|
+
out[i * 4] = Math.cos(a) * rr
|
|
211
|
+
out[i * 4 + 1] = st.y + gauss() * st.half * 0.9
|
|
212
|
+
out[i * 4 + 2] = Math.sin(a) * rr
|
|
213
|
+
out[i * 4 + 3] = rnd() * Math.PI * 2
|
|
214
|
+
}
|
|
215
|
+
return out
|
|
216
|
+
}) as [Float32Array, Float32Array, Float32Array]
|
|
217
|
+
|
|
218
|
+
return { count: n, x, y, z, r, parent, group, kind, segA, segB, segCount: s, sites, dust }
|
|
219
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/* xujie-memory panel. Every selector is prefixed .xjm-*, mounted while the plugin lives. */
|
|
2
|
+
.xjm-root {
|
|
3
|
+
--xjm-fg: var(--dsw-alias-label-primary, #1b2230);
|
|
4
|
+
--xjm-fg2: var(--dsw-alias-label-secondary, #4a5468);
|
|
5
|
+
/* Host tertiary (#81858c) is 3.7:1 on white; blend toward secondary so muted text stays >= 4.5:1 on white and hover fills. */
|
|
6
|
+
--xjm-fg3: color-mix(in srgb, var(--dsw-alias-label-tertiary, #81858c) 25%, var(--dsw-alias-label-secondary, #61666b));
|
|
7
|
+
--xjm-bg: var(--dsw-alias-bg-base, #ffffff);
|
|
8
|
+
--xjm-surface: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
9
|
+
--xjm-line: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
10
|
+
--xjm-line-soft: var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.05));
|
|
11
|
+
--xjm-hover: var(--dsw-alias-interactive-bg-hover, rgba(38, 49, 72, 0.06));
|
|
12
|
+
--xjm-danger: var(--dsw-alias-state-error-primary, #ec1313);
|
|
13
|
+
--xjm-danger-ink: color-mix(in srgb, var(--xjm-danger) 85%, #000); /* text on light surfaces */
|
|
14
|
+
--xjm-danger-fill: color-mix(in srgb, var(--xjm-danger) 75%, #000); /* filled button behind white text */
|
|
15
|
+
--xjm-warn: var(--dsw-alias-state-warn-primary, #d68a12);
|
|
16
|
+
--xjm-sem: #3a66d6;
|
|
17
|
+
--xjm-epi: #8f6210;
|
|
18
|
+
--xjm-work: #0f7a6a;
|
|
19
|
+
container: xjm / inline-size;
|
|
20
|
+
position: relative;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
min-width: 0;
|
|
24
|
+
min-height: 0;
|
|
25
|
+
/* The root is its own query container, so a container query cannot switch its overflow.
|
|
26
|
+
It always owns vertical scrolling; on desktop the shell is exactly 100% tall, so nothing scrolls. */
|
|
27
|
+
overflow-x: hidden;
|
|
28
|
+
overflow-y: auto;
|
|
29
|
+
overscroll-behavior: contain;
|
|
30
|
+
-webkit-overflow-scrolling: touch;
|
|
31
|
+
color: var(--xjm-fg);
|
|
32
|
+
background: var(--xjm-bg);
|
|
33
|
+
font-family: var(--dsw-font-family, ui-sans-serif, system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif);
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
line-height: 1.5;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
}
|
|
38
|
+
body[data-ds-dark-theme] .xjm-root { --xjm-sem: #8aaeff; --xjm-epi: #efc683; --xjm-work: #62dcc8; --xjm-danger-ink: var(--xjm-danger); }
|
|
39
|
+
.xjm-root *, .xjm-root *::before, .xjm-root *::after { box-sizing: border-box; }
|
|
40
|
+
:where(.xjm-root) :where(button, input, select, textarea) { font: inherit; color: inherit; }
|
|
41
|
+
.xjm-root :focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, #4c8dff); outline-offset: 2px; }
|
|
42
|
+
.xjm-root [data-layer="0"] { --xjm-lc: var(--xjm-sem); }
|
|
43
|
+
.xjm-root [data-layer="1"] { --xjm-lc: var(--xjm-epi); }
|
|
44
|
+
.xjm-root [data-layer="2"] { --xjm-lc: var(--xjm-work); }
|
|
45
|
+
.xjm-visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
46
|
+
|
|
47
|
+
.xjm-shell { display: flex; width: 100%; height: 100%; min-height: 0; }
|
|
48
|
+
|
|
49
|
+
/* ---------- stage: an always-dark instrument viewport ---------- */
|
|
50
|
+
.xjm-stage {
|
|
51
|
+
position: relative;
|
|
52
|
+
flex: 1 1 auto;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
margin: 10px 0 10px 10px;
|
|
55
|
+
border-radius: 12px;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
isolation: isolate;
|
|
58
|
+
color-scheme: dark;
|
|
59
|
+
background:
|
|
60
|
+
radial-gradient(70% 60% at 50% 48%, rgba(74, 96, 150, 0.16) 0%, rgba(74, 96, 150, 0) 70%),
|
|
61
|
+
radial-gradient(120% 95% at 50% 45%, #10151f 0%, #0a0d13 55%, #06070a 100%);
|
|
62
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
|
63
|
+
}
|
|
64
|
+
.xjm-stage::after {
|
|
65
|
+
content: "";
|
|
66
|
+
position: absolute;
|
|
67
|
+
inset: 0;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
background: radial-gradient(110% 90% at 50% 50%, transparent 62%, rgba(0, 0, 0, 0.38) 100%);
|
|
70
|
+
z-index: 1;
|
|
71
|
+
}
|
|
72
|
+
.xjm-field { position: absolute; inset: 0; }
|
|
73
|
+
.xjm-stage::before {
|
|
74
|
+
content: "";
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
z-index: 1;
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
background:
|
|
80
|
+
linear-gradient(180deg, rgba(7, 9, 13, 0.78) 0, rgba(7, 9, 13, 0.32) 76px, rgba(7, 9, 13, 0) 120px),
|
|
81
|
+
linear-gradient(0deg, rgba(7, 9, 13, 0.8) 0, rgba(7, 9, 13, 0.3) 40px, rgba(7, 9, 13, 0) 76px);
|
|
82
|
+
}
|
|
83
|
+
.xjm-canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; outline: none; }
|
|
84
|
+
.xjm-canvas:active { cursor: grabbing; }
|
|
85
|
+
.xjm-canvas:focus-visible { box-shadow: inset 0 0 0 2px rgba(138, 174, 255, 0.55); }
|
|
86
|
+
.xjm-field-fallback { position: absolute; inset: auto 24px 50% 24px; margin: 0; text-align: center; color: rgba(225, 232, 245, 0.75); font-size: 14px; }
|
|
87
|
+
|
|
88
|
+
.xjm-tip {
|
|
89
|
+
position: absolute; left: 0; top: 0; z-index: 4;
|
|
90
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
91
|
+
max-width: 280px; padding: 8px 10px;
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
background: rgba(13, 17, 26, 0.9);
|
|
94
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
95
|
+
backdrop-filter: blur(8px);
|
|
96
|
+
color: rgba(236, 241, 250, 0.94);
|
|
97
|
+
font-size: 14px; line-height: 1.5;
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
}
|
|
100
|
+
.xjm-tip[hidden] { display: none; }
|
|
101
|
+
.xjm-tip-kind { font-size: 14px; font-weight: 600; letter-spacing: 0.04em; }
|
|
102
|
+
.xjm-tip-kind[data-layer="0"] { color: #a3bdff; }
|
|
103
|
+
.xjm-tip-kind[data-layer="1"] { color: #f1cd92; }
|
|
104
|
+
.xjm-tip-kind[data-layer="2"] { color: #7fe6d4; }
|
|
105
|
+
.xjm-tip-text { word-break: break-word; }
|
|
106
|
+
|
|
107
|
+
.xjm-stage-top {
|
|
108
|
+
position: absolute; top: 14px; left: 22px; right: 14px; z-index: 2;
|
|
109
|
+
display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 16px;
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
}
|
|
112
|
+
.xjm-stage-head { min-width: 0; padding-top: 4px; pointer-events: none; }
|
|
113
|
+
.xjm-eyebrow { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: 0.12em; color: rgba(170, 188, 220, 0.86); }
|
|
114
|
+
.xjm-title { margin: 2px 0 0; font-size: 22px; line-height: 1.3; font-weight: 600; color: #eef2f9; }
|
|
115
|
+
.xjm-sub { margin: 2px 0 0; font-size: 14px; color: rgba(206, 215, 232, 0.8); font-variant-numeric: tabular-nums; }
|
|
116
|
+
|
|
117
|
+
.xjm-controls {
|
|
118
|
+
margin-left: auto; pointer-events: auto;
|
|
119
|
+
display: flex; align-items: center; gap: 2px; padding: 3px;
|
|
120
|
+
border-radius: 10px;
|
|
121
|
+
background: rgba(15, 19, 28, 0.72);
|
|
122
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
123
|
+
backdrop-filter: blur(10px);
|
|
124
|
+
}
|
|
125
|
+
.xjm-tool { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: rgba(224, 231, 244, 0.82); cursor: pointer; }
|
|
126
|
+
.xjm-tool:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); color: #fff; }
|
|
127
|
+
.xjm-tool:disabled { opacity: 0.38; cursor: default; }
|
|
128
|
+
.xjm-tool-sep { width: 1px; height: 22px; margin: 0 3px; background: rgba(255, 255, 255, 0.12); }
|
|
129
|
+
|
|
130
|
+
.xjm-legend {
|
|
131
|
+
position: absolute; left: 22px; bottom: 16px; z-index: 2;
|
|
132
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px;
|
|
133
|
+
max-width: calc(100% - 44px);
|
|
134
|
+
font-size: 14px; color: rgba(214, 222, 236, 0.86);
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
}
|
|
137
|
+
.xjm-legend span { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
|
|
138
|
+
.xjm-legend .xjm-legend-quiet { color: rgba(190, 202, 224, 0.76); white-space: normal; }
|
|
139
|
+
.xjm-g-dot { width: 7px; height: 7px; border-radius: 50%; background: #e4eaf7; box-shadow: 0 0 6px rgba(200, 215, 255, 0.7); }
|
|
140
|
+
.xjm-g-diamond { width: 7px; height: 7px; transform: rotate(45deg); box-shadow: inset 0 0 0 1.5px #e4eaf7; }
|
|
141
|
+
.xjm-g-link { width: 18px; height: 1.5px; border-radius: 1px; background: linear-gradient(90deg, #efc683, #8aaeff); }
|
|
142
|
+
|
|
143
|
+
.xjm-overlay { position: absolute; inset: 0; z-index: 3; display: grid; place-items: center; padding: 24px; pointer-events: none; }
|
|
144
|
+
.xjm-overlay .xjm-status {
|
|
145
|
+
pointer-events: auto;
|
|
146
|
+
max-width: 360px; padding: 22px 26px;
|
|
147
|
+
border-radius: 12px;
|
|
148
|
+
background: rgba(11, 14, 21, 0.78);
|
|
149
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 16px 40px rgba(0, 0, 0, 0.35);
|
|
150
|
+
backdrop-filter: blur(12px);
|
|
151
|
+
color: #e9eef7;
|
|
152
|
+
}
|
|
153
|
+
.xjm-overlay .xjm-status-body { color: rgba(214, 222, 238, 0.72); }
|
|
154
|
+
.xjm-overlay .xjm-status-icon { color: #f0c987; }
|
|
155
|
+
|
|
156
|
+
/* ---------- inspector ---------- */
|
|
157
|
+
.xjm-inspector { position: relative; display: flex; flex-direction: column; flex: 0 0 344px; width: 344px; min-width: 0; min-height: 0; }
|
|
158
|
+
.xjm-insp-head { display: flex; flex-direction: column; gap: 10px; padding: 14px 14px 0; }
|
|
159
|
+
.xjm-scope { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
160
|
+
.xjm-scope-label { flex: none; font-size: 14px; color: var(--xjm-fg3); }
|
|
161
|
+
.xjm-select {
|
|
162
|
+
flex: 1 1 auto; min-width: 0; height: 44px; padding: 0 30px 0 12px;
|
|
163
|
+
border: 1px solid var(--xjm-line); border-radius: 8px;
|
|
164
|
+
background-color: var(--xjm-surface);
|
|
165
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
166
|
+
background-position: calc(100% - 15px) 14px, calc(100% - 11px) 14px;
|
|
167
|
+
background-size: 4px 4px; background-repeat: no-repeat;
|
|
168
|
+
appearance: none; -webkit-appearance: none;
|
|
169
|
+
font-size: 14px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; cursor: pointer;
|
|
170
|
+
}
|
|
171
|
+
.xjm-select:disabled { opacity: 0.55; cursor: default; }
|
|
172
|
+
.xjm-search {
|
|
173
|
+
display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 0 0 12px;
|
|
174
|
+
border: 1px solid var(--xjm-line); border-radius: 8px; background: var(--xjm-surface); color: var(--xjm-fg3);
|
|
175
|
+
}
|
|
176
|
+
.xjm-search:focus-within { border-color: var(--xjm-fg3); box-shadow: 0 0 0 2px color-mix(in srgb, var(--xjm-fg3) 28%, transparent); }
|
|
177
|
+
.xjm-search-input:focus-visible { outline: none; }
|
|
178
|
+
.xjm-search-input { flex: 1 1 auto; min-width: 0; height: calc(100% + 2px); margin: -1px 0; padding: 0; border: 0; outline: none; background: transparent; color: var(--xjm-fg); font-size: 14px; }
|
|
179
|
+
.xjm-search-input::placeholder { color: var(--xjm-fg3); opacity: 1; }
|
|
180
|
+
.xjm-search-input::-webkit-search-cancel-button { display: none; }
|
|
181
|
+
.xjm-search-input:disabled { cursor: default; }
|
|
182
|
+
.xjm-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--xjm-line); }
|
|
183
|
+
.xjm-tab { display: inline-flex; align-items: center; gap: 6px; height: 44px; margin-bottom: -1px; padding: 0 10px; border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--xjm-fg2); font-size: 14px; cursor: pointer; }
|
|
184
|
+
.xjm-tab:hover { color: var(--xjm-fg); }
|
|
185
|
+
.xjm-tab[aria-selected="true"] { color: var(--xjm-fg); font-weight: 600; border-bottom-color: var(--xjm-fg); }
|
|
186
|
+
.xjm-tab-count { min-width: 22px; padding: 0 6px; border-radius: 11px; background: var(--xjm-hover); font-size: 14px; line-height: 22px; text-align: center; font-weight: 500; }
|
|
187
|
+
.xjm-insp-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 14px 72px; scrollbar-gutter: stable; }
|
|
188
|
+
|
|
189
|
+
.xjm-section + .xjm-section { margin-top: 22px; }
|
|
190
|
+
.xjm-h2 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--xjm-fg3); letter-spacing: 0.02em; }
|
|
191
|
+
.xjm-note + .xjm-section { margin-top: 16px; }
|
|
192
|
+
.xjm-note { margin: 10px 0 0; font-size: 14px; line-height: 1.6; color: var(--xjm-fg3); }
|
|
193
|
+
.xjm-empty-line { margin: 6px 0; font-size: 14px; color: var(--xjm-fg3); }
|
|
194
|
+
.xjm-inline-error { margin: 6px 0; font-size: 14px; color: var(--xjm-danger-ink); }
|
|
195
|
+
.xjm-result-count { margin: 0 0 6px; font-size: 14px; color: var(--xjm-fg3); }
|
|
196
|
+
.xjm-spacer { flex: 1 1 auto; }
|
|
197
|
+
|
|
198
|
+
.xjm-layers { display: flex; flex-direction: column; gap: 6px; }
|
|
199
|
+
.xjm-layer {
|
|
200
|
+
display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px;
|
|
201
|
+
border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-surface);
|
|
202
|
+
text-align: left; cursor: pointer;
|
|
203
|
+
}
|
|
204
|
+
.xjm-layer:hover:not(:disabled) { background: var(--xjm-hover); }
|
|
205
|
+
.xjm-layer:disabled { cursor: default; opacity: 0.6; }
|
|
206
|
+
.xjm-layer[aria-pressed="true"] { border-color: var(--xjm-lc); background: color-mix(in srgb, var(--xjm-lc) 9%, var(--xjm-surface)); }
|
|
207
|
+
.xjm-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--xjm-lc); box-shadow: 0 0 0 3px color-mix(in srgb, var(--xjm-lc) 18%, transparent); }
|
|
208
|
+
.xjm-layer-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
|
|
209
|
+
.xjm-layer-name { font-size: 14px; font-weight: 500; }
|
|
210
|
+
.xjm-layer-hint { font-size: 14px; color: var(--xjm-fg3); }
|
|
211
|
+
.xjm-layer-count { font-size: 15px; font-weight: 600; color: var(--xjm-fg2); font-variant-numeric: tabular-nums; }
|
|
212
|
+
|
|
213
|
+
.xjm-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: 0; overflow: hidden; border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-line); }
|
|
214
|
+
.xjm-stats > div { padding: 8px 10px; background: var(--xjm-bg); }
|
|
215
|
+
.xjm-stats dt { font-size: 14px; color: var(--xjm-fg3); }
|
|
216
|
+
.xjm-stats dd { margin: 0; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
217
|
+
|
|
218
|
+
.xjm-list { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
|
|
219
|
+
.xjm-row { display: flex; flex-direction: column; gap: 3px; width: 100%; min-height: 44px; padding: 10px 10px; border: 0; border-radius: 8px; background: transparent; text-align: left; cursor: pointer; }
|
|
220
|
+
.xjm-row:hover { background: var(--xjm-hover); }
|
|
221
|
+
.xjm-row[aria-current="true"] { background: var(--xjm-hover); box-shadow: inset 2px 0 0 var(--xjm-fg2); }
|
|
222
|
+
.xjm-row-compact { gap: 2px; padding: 8px 10px; }
|
|
223
|
+
.xjm-row-lead { display: flex; align-items: center; gap: 8px; }
|
|
224
|
+
.xjm-row-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font-size: 14px; line-height: 1.5; word-break: break-word; }
|
|
225
|
+
.xjm-row-text-1 { -webkit-line-clamp: 1; font-size: 14px; color: var(--xjm-fg2); }
|
|
226
|
+
.xjm-row-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 14px; color: var(--xjm-fg3); font-variant-numeric: tabular-nums; }
|
|
227
|
+
.xjm-row-meta svg { margin-left: auto; }
|
|
228
|
+
.xjm-badge { display: inline-flex; align-items: center; height: 22px; padding: 0 7px; border-radius: 5px; background: color-mix(in srgb, var(--xjm-fg) 7%, transparent); color: var(--xjm-fg2); font-size: 14px; font-weight: 500; white-space: nowrap; }
|
|
229
|
+
.xjm-badge-muted { background: transparent; box-shadow: inset 0 0 0 1px var(--xjm-line); color: var(--xjm-fg3); }
|
|
230
|
+
.xjm-mark { padding: 0 1px; border-radius: 2px; background: color-mix(in srgb, #efc683 45%, transparent); color: inherit; }
|
|
231
|
+
|
|
232
|
+
.xjm-detail-head { display: flex; align-items: center; gap: 8px; margin: -4px 0 12px; min-width: 0; }
|
|
233
|
+
.xjm-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; color: var(--xjm-lc); white-space: nowrap; }
|
|
234
|
+
.xjm-chip .xjm-dot { width: 6px; height: 6px; box-shadow: none; }
|
|
235
|
+
.xjm-kind { font-size: 14px; color: var(--xjm-fg2); white-space: nowrap; }
|
|
236
|
+
.xjm-icon-btn { display: grid; place-items: center; flex: none; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--xjm-fg2); cursor: pointer; }
|
|
237
|
+
.xjm-icon-btn:hover { background: var(--xjm-hover); color: var(--xjm-fg); }
|
|
238
|
+
.xjm-icon-btn-sm { width: 44px; height: 44px; }
|
|
239
|
+
.xjm-content {
|
|
240
|
+
margin: 0 0 12px; padding: 12px 14px;
|
|
241
|
+
border: 1px solid var(--xjm-line-soft); border-radius: 10px;
|
|
242
|
+
background: color-mix(in srgb, var(--xjm-fg) 3.5%, transparent);
|
|
243
|
+
font-size: 15px; line-height: 1.65; white-space: pre-wrap; word-break: break-word;
|
|
244
|
+
}
|
|
245
|
+
.xjm-editor { margin: 0 0 16px; }
|
|
246
|
+
.xjm-textarea {
|
|
247
|
+
display: block; width: 100%; min-height: 156px; resize: vertical;
|
|
248
|
+
padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l3, var(--xjm-line)); border-radius: 10px;
|
|
249
|
+
background: var(--xjm-surface); font-size: 15px; line-height: 1.6;
|
|
250
|
+
}
|
|
251
|
+
.xjm-textarea:focus { outline: none; border-color: var(--xjm-fg3); box-shadow: 0 0 0 3px color-mix(in srgb, var(--xjm-fg) 8%, transparent); }
|
|
252
|
+
.xjm-editor-foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
253
|
+
.xjm-count { font-size: 14px; color: var(--xjm-fg3); font-variant-numeric: tabular-nums; }
|
|
254
|
+
.xjm-count-bad { color: var(--xjm-danger-ink); }
|
|
255
|
+
.xjm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
|
|
256
|
+
|
|
257
|
+
.xjm-btn {
|
|
258
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; padding: 0 16px;
|
|
259
|
+
border: 1px solid var(--xjm-line); border-radius: 8px; background: var(--xjm-surface);
|
|
260
|
+
font-size: 14px; font-weight: 500; white-space: nowrap; cursor: pointer;
|
|
261
|
+
}
|
|
262
|
+
.xjm-btn:hover:not(:disabled) { background: var(--xjm-hover); }
|
|
263
|
+
.xjm-btn:disabled { opacity: 0.45; cursor: default; }
|
|
264
|
+
.xjm-btn-primary { border-color: transparent; background: var(--dsw-alias-button-primary-fill, var(--xjm-fg)); color: var(--dsw-alias-label-primary-inverted, #fff); }
|
|
265
|
+
.xjm-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-fill, var(--xjm-fg)); filter: brightness(1.12); }
|
|
266
|
+
.xjm-btn-danger { border-color: transparent; background: var(--xjm-danger-fill); color: #fff; }
|
|
267
|
+
.xjm-btn-danger:hover:not(:disabled) { background: var(--xjm-danger-fill); filter: brightness(1.08); }
|
|
268
|
+
.xjm-btn-danger-quiet { color: var(--xjm-danger-ink); }
|
|
269
|
+
.xjm-btn-danger-quiet:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger, color-mix(in srgb, var(--xjm-danger) 9%, transparent)); }
|
|
270
|
+
.xjm-btn-ghost-dark { margin-top: 6px; border-color: rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.06); color: #eef2f9; }
|
|
271
|
+
.xjm-btn-ghost-dark:hover:not(:disabled) { background: rgba(255, 255, 255, 0.12); }
|
|
272
|
+
|
|
273
|
+
.xjm-confirm { margin: 0 0 16px; padding: 12px 14px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--xjm-danger) 35%, transparent); background: color-mix(in srgb, var(--xjm-danger) 6%, transparent); }
|
|
274
|
+
.xjm-confirm-title { margin: 0; font-size: 14px; font-weight: 600; }
|
|
275
|
+
.xjm-confirm-body { margin: 4px 0 12px; font-size: 14px; line-height: 1.6; color: var(--xjm-fg2); }
|
|
276
|
+
.xjm-confirm .xjm-actions { margin: 0; justify-content: flex-end; }
|
|
277
|
+
|
|
278
|
+
.xjm-banner { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0 0 12px; padding: 10px 12px; border-radius: 10px; font-size: 14px; line-height: 1.55; }
|
|
279
|
+
.xjm-banner p { margin: 0; color: var(--xjm-fg2); }
|
|
280
|
+
.xjm-banner b { display: block; color: var(--xjm-fg); font-weight: 600; }
|
|
281
|
+
.xjm-banner-warn { border: 1px solid color-mix(in srgb, var(--xjm-warn) 45%, transparent); background: color-mix(in srgb, var(--xjm-warn) 11%, transparent); }
|
|
282
|
+
.xjm-banner-error { border: 1px solid color-mix(in srgb, var(--xjm-danger) 40%, transparent); background: color-mix(in srgb, var(--xjm-danger) 8%, transparent); }
|
|
283
|
+
|
|
284
|
+
.xjm-meta { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 16px; margin: 0 0 18px; font-size: 14px; }
|
|
285
|
+
.xjm-meta > div { display: contents; }
|
|
286
|
+
.xjm-meta dt { color: var(--xjm-fg3); }
|
|
287
|
+
.xjm-meta dd { margin: 0; color: var(--xjm-fg2); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
288
|
+
.xjm-mono { font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); font-size: 14px; }
|
|
289
|
+
|
|
290
|
+
.xjm-status { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 28px 12px; text-align: center; }
|
|
291
|
+
.xjm-status-icon { color: var(--xjm-fg3); }
|
|
292
|
+
.xjm-status-title { margin: 0; font-size: 15px; font-weight: 600; }
|
|
293
|
+
.xjm-status-body { margin: 0; font-size: 14px; line-height: 1.6; color: var(--xjm-fg2); }
|
|
294
|
+
.xjm-status .xjm-btn { margin-top: 8px; }
|
|
295
|
+
|
|
296
|
+
.xjm-card { margin-top: 10px; padding: 12px 14px; border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-surface); }
|
|
297
|
+
.xjm-card-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
298
|
+
.xjm-avatar { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border-radius: 50%; background: color-mix(in srgb, var(--xjm-sem) 15%, transparent); color: var(--xjm-sem); font-size: 14px; font-weight: 600; }
|
|
299
|
+
.xjm-card-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; }
|
|
300
|
+
.xjm-card-status { margin-left: auto; font-size: 14px; color: var(--xjm-fg3); white-space: nowrap; }
|
|
301
|
+
.xjm-tag-group { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 8px; margin-top: 10px; align-items: start; }
|
|
302
|
+
.xjm-tag-kind { padding-top: 2px; font-size: 14px; color: var(--xjm-fg3); }
|
|
303
|
+
.xjm-tags { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
304
|
+
.xjm-tag { padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--xjm-fg) 6%, transparent); font-size: 14px; line-height: 1.5; word-break: break-word; }
|
|
305
|
+
|
|
306
|
+
.xjm-skeleton { padding: 4px 0; }
|
|
307
|
+
.xjm-skeleton i { display: block; height: 12px; margin: 12px 0; border-radius: 6px; background: var(--dsw-alias-bg-skeleton, color-mix(in srgb, var(--xjm-fg) 8%, transparent)); animation: xjm-pulse 1.6s ease-in-out infinite; }
|
|
308
|
+
.xjm-skeleton i:nth-child(1) { width: 92%; } .xjm-skeleton i:nth-child(2) { width: 76%; } .xjm-skeleton i:nth-child(3) { width: 84%; } .xjm-skeleton i:nth-child(4) { width: 58%; }
|
|
309
|
+
.xjm-spinner { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 2px solid color-mix(in srgb, currentColor 22%, transparent); border-top-color: currentColor; animation: xjm-spin 0.9s linear infinite; }
|
|
310
|
+
.xjm-overlay .xjm-spinner { color: #a9c0ff; }
|
|
311
|
+
.xjm-spinner-sm { flex: none; width: 14px; height: 14px; }
|
|
312
|
+
@keyframes xjm-spin { to { transform: rotate(360deg); } }
|
|
313
|
+
@keyframes xjm-pulse { 50% { opacity: 0.45; } }
|
|
314
|
+
|
|
315
|
+
.xjm-toast-slot { position: absolute; left: 14px; right: 14px; bottom: 14px; pointer-events: none; }
|
|
316
|
+
.xjm-toast { padding: 10px 12px; border-radius: 10px; background: var(--xjm-fg); color: var(--xjm-bg); font-size: 14px; line-height: 1.5; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 8px 20px rgba(0, 0, 0, 0.14); }
|
|
317
|
+
|
|
318
|
+
@media (prefers-reduced-motion: reduce) {
|
|
319
|
+
.xjm-spinner, .xjm-skeleton i { animation: none; }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@container xjm (max-width: 1040px) {
|
|
323
|
+
.xjm-inspector { flex-basis: 304px; width: 304px; }
|
|
324
|
+
}
|
|
325
|
+
@container xjm (max-width: 760px) {
|
|
326
|
+
.xjm-shell { flex-direction: column; height: auto; min-height: 100%; }
|
|
327
|
+
.xjm-stage { flex: none; height: 580px; margin: 8px 8px 0; }
|
|
328
|
+
/* Narrow/touch: vertical swipes on the neuron scroll the panel; horizontal drags still rotate. Zoom via toolbar. */
|
|
329
|
+
.xjm-canvas { touch-action: pan-y; }
|
|
330
|
+
.xjm-stage-top { top: 10px; left: 16px; right: 10px; }
|
|
331
|
+
.xjm-title { font-size: 20px; }
|
|
332
|
+
.xjm-legend { left: 16px; bottom: 12px; max-width: calc(100% - 32px); gap: 4px 12px; }
|
|
333
|
+
.xjm-inspector { flex: none; width: auto; }
|
|
334
|
+
.xjm-insp-head { padding: 14px 16px 0; }
|
|
335
|
+
.xjm-insp-body { overflow: visible; padding: 14px 16px 88px; }
|
|
336
|
+
.xjm-toast-slot { position: fixed; left: 16px; right: 16px; bottom: 16px; }
|
|
337
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side mirror of the Host DTOs (release src/types.ts). Kept structural so the
|
|
3
|
+
* client tree compiles standalone; the parent may replace this import with
|
|
4
|
+
* `../types.ts` when merging into release-0.7.0/src/client.
|
|
5
|
+
*/
|
|
6
|
+
export type { Catalog, Snapshot, Trace, SearchHit, EditResult, ForgetResult, PersonCard, PersonTag, Node, Edge, Stats, ScopeRow } from '../types.ts'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
3
|
+
import type { Catalog, Snapshot, Trace, SearchHit, EditResult, ForgetResult, PersonCard } from './types.ts'
|
|
4
|
+
|
|
5
|
+
/** Trusted Node-only bridge; it is never exported to the browser. */
|
|
6
|
+
export interface KernelBridge {
|
|
7
|
+
request(path: string, body: Record<string, string | number>): Promise<any>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
11
|
+
interface RemoteErrorDetailsMap {
|
|
12
|
+
'xujie-memory/owner-required': { action: string }
|
|
13
|
+
'xujie-memory/rejected': { reason: string }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Memory management is private to the authenticated DSH owner, not shared sessions. */
|
|
18
|
+
export class MemoryController extends TypertRemoteService {
|
|
19
|
+
constructor(ctx: Context, private readonly bridge: KernelBridge) {
|
|
20
|
+
super(ctx, 'xujieMemory')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private owner(): void {
|
|
24
|
+
if (this.ctx.invocation?.peer.principal.kind !== 'owner') {
|
|
25
|
+
throw new RemoteError('xujie-memory/owner-required', '只有 DSH 所有者可以管理记忆库', { action: 'memory/manage' })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private async call(path: string, body: Record<string, string | number>): Promise<any> {
|
|
30
|
+
this.owner()
|
|
31
|
+
try { return await this.bridge.request(path, body) }
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new RemoteError('xujie-memory/rejected', '记忆库操作未完成', {
|
|
34
|
+
reason: error instanceof Error ? error.message : 'kernel_unavailable',
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Remote
|
|
40
|
+
async catalog(): Promise<Catalog> {
|
|
41
|
+
const r = await this.call('/manage/catalog', {})
|
|
42
|
+
return { scope_mode: r.scope_mode, scopes: r.scopes }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Remote
|
|
46
|
+
async snapshot(scopeId: string): Promise<Snapshot> {
|
|
47
|
+
const r = await this.call('/manage/snapshot', { scope_id: scopeId })
|
|
48
|
+
const cards: PersonCard[] = r.cards.map((c: any) => ({
|
|
49
|
+
account_id: c.profile.account_id, display_name: c.profile.display_name ?? c.profile.account_id,
|
|
50
|
+
status: c.profile.status, tags: c.tags.map((t: any) => ({
|
|
51
|
+
tag_id: t.tag_id, kind: t.kind, value: t.value, source: t.source, confidence: t.confidence,
|
|
52
|
+
})),
|
|
53
|
+
}))
|
|
54
|
+
const { scope_id, scope_mode, nodes, edges, truncated, limit } = r
|
|
55
|
+
// Lifecycle statistics may carry an internal scope key; do not copy it to DTO.
|
|
56
|
+
const { traces, traces_erased, events, states, anchors, clarifications_open, summaries } = r.stats
|
|
57
|
+
return { scope_id, scope_mode, nodes, edges, cards, truncated, limit,
|
|
58
|
+
stats: { traces, traces_erased, events, states, anchors, clarifications_open, summaries } }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Remote
|
|
62
|
+
async detail(scopeId: string, traceId: string): Promise<Trace> {
|
|
63
|
+
return (await this.call('/manage/detail', { scope_id: scopeId, trace_id: traceId })).trace
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Remote
|
|
67
|
+
async search(scopeId: string, query: string): Promise<SearchHit[]> {
|
|
68
|
+
const r = await this.call('/manage/search', { scope_id: scopeId, query })
|
|
69
|
+
return r.hits.map((h: any) => ({
|
|
70
|
+
trace_id: h.trace_id, content: h.content, content_hash: h.content_hash,
|
|
71
|
+
score: h.score, priority: Number(h.priority ?? 0), importance: Number(h.importance ?? 1),
|
|
72
|
+
anchor: Boolean(h.anchor), recorded_at: h.recorded_at ?? '', layer: h.layer,
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Remote
|
|
77
|
+
async edit(scopeId: string, traceId: string, content: string, expectedHash: string): Promise<EditResult> {
|
|
78
|
+
const r = await this.call('/manage/edit', { scope_id: scopeId, trace_id: traceId, content, expected_hash: expectedHash })
|
|
79
|
+
return { changed: r.changed, trace: r.trace,
|
|
80
|
+
...(r.replaced_trace_id ? { replaced_trace_id: r.replaced_trace_id, pending: r.pending } : {}) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Remote
|
|
84
|
+
async forget(scopeId: string, traceId: string, expectedHash: string): Promise<ForgetResult> {
|
|
85
|
+
const r = await this.call('/manage/forget', { scope_id: scopeId, trace_id: traceId, expected_hash: expectedHash })
|
|
86
|
+
return { trace_id: r.trace_id, erased: r.erased, already_erased: r.already_erased, state_keys: r.state_keys }
|
|
87
|
+
}
|
|
88
|
+
}
|