@musnows/scriverse 0.3.5 → 0.3.6
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/dist/ai.js +223 -15
- package/dist/ai.js.map +1 -1
- package/dist/app.js +129 -9
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +162 -0
- package/dist/attachment-storage.js.map +1 -0
- package/dist/cli-contract.js +4 -4
- package/dist/cli-contract.js.map +1 -1
- package/dist/database.js +219 -1
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +328 -29
- package/dist/public/index.html +2 -2
- package/dist/public/markdown.js +16 -1
- package/dist/public/race-hierarchy.js +49 -0
- package/dist/public/relationship-graph.js +205 -31
- package/dist/public/styles.css +88 -3
- package/dist/server-runtime.js +1 -0
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +565 -21
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +7 -0
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function racePathLabel(race) {
|
|
2
|
+
const names = Array.isArray(race?.lineage)
|
|
3
|
+
? race.lineage.map((item) => String(item?.name ?? "").trim()).filter(Boolean)
|
|
4
|
+
: [];
|
|
5
|
+
return names.length ? names.join(" / ") : String(race?.name ?? "").trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function raceDescendantIds(races, raceId) {
|
|
9
|
+
const childrenByParent = new Map();
|
|
10
|
+
for (const race of races) {
|
|
11
|
+
const parentRaceId = race?.parentRaceId == null ? null : String(race.parentRaceId);
|
|
12
|
+
const children = childrenByParent.get(parentRaceId) ?? [];
|
|
13
|
+
children.push(race);
|
|
14
|
+
childrenByParent.set(parentRaceId, children);
|
|
15
|
+
}
|
|
16
|
+
const descendants = new Set();
|
|
17
|
+
const pending = [...(childrenByParent.get(String(raceId)) ?? [])];
|
|
18
|
+
while (pending.length) {
|
|
19
|
+
const race = pending.pop();
|
|
20
|
+
const id = String(race?.id ?? "");
|
|
21
|
+
if (!id || descendants.has(id)) continue;
|
|
22
|
+
descendants.add(id);
|
|
23
|
+
pending.push(...(childrenByParent.get(id) ?? []));
|
|
24
|
+
}
|
|
25
|
+
return descendants;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function eligibleRaceParents(races, currentRaceId = null) {
|
|
29
|
+
if (!currentRaceId) return [...races];
|
|
30
|
+
const excluded = raceDescendantIds(races, currentRaceId);
|
|
31
|
+
excluded.add(String(currentRaceId));
|
|
32
|
+
return races.filter((race) => !excluded.has(String(race?.id ?? "")));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildRaceForest(races) {
|
|
36
|
+
const nodes = new Map(races.map((race) => [String(race.id), { ...race, children: [] }]));
|
|
37
|
+
const roots = [];
|
|
38
|
+
for (const node of nodes.values()) {
|
|
39
|
+
const parent = node.parentRaceId ? nodes.get(String(node.parentRaceId)) : null;
|
|
40
|
+
if (parent && parent !== node) parent.children.push(node);
|
|
41
|
+
else roots.push(node);
|
|
42
|
+
}
|
|
43
|
+
const sort = (items) => {
|
|
44
|
+
items.sort((left, right) => String(left.name).localeCompare(String(right.name), "zh-CN"));
|
|
45
|
+
for (const item of items) sort(item.children);
|
|
46
|
+
return items;
|
|
47
|
+
};
|
|
48
|
+
return sort(roots);
|
|
49
|
+
}
|
|
@@ -21,12 +21,29 @@ const NETWORK_LAYOUTS = Object.freeze({
|
|
|
21
21
|
standard: Object.freeze({ width: 1200, height: 640, marginX: 48, marginY: 42, desiredEdgeLength: 196, repulsionStrength: 16800 }),
|
|
22
22
|
expanded: Object.freeze({ width: 1600, height: 900, marginX: 64, marginY: 56, desiredEdgeLength: 236, repulsionStrength: 22800 })
|
|
23
23
|
});
|
|
24
|
+
const RELATIONSHIP_EDGE_GAP = 24;
|
|
25
|
+
let relationshipRendererSequence = 0;
|
|
26
|
+
const GALAXY_CELESTIAL_PALETTES = Object.freeze([
|
|
27
|
+
Object.freeze({ key: "solar", hue: 42, saturation: 96, lightness: 68, color: "#ffc95f", core: "#fff8d4", rim: "#9f3c18", atmosphere: "rgba(255,184,72,.58)", ring: "rgba(255,222,151,.72)" }),
|
|
28
|
+
Object.freeze({ key: "azure", hue: 211, saturation: 94, lightness: 68, color: "#61b8ff", core: "#effaff", rim: "#173b85", atmosphere: "rgba(79,156,255,.56)", ring: "rgba(164,214,255,.68)" }),
|
|
29
|
+
Object.freeze({ key: "violet", hue: 263, saturation: 82, lightness: 72, color: "#b58cff", core: "#f7edff", rim: "#4d237c", atmosphere: "rgba(151,92,255,.54)", ring: "rgba(219,190,255,.7)" }),
|
|
30
|
+
Object.freeze({ key: "rose", hue: 342, saturation: 91, lightness: 70, color: "#ff739d", core: "#fff0f5", rim: "#7f1e42", atmosphere: "rgba(255,91,139,.52)", ring: "rgba(255,190,210,.68)" }),
|
|
31
|
+
Object.freeze({ key: "emerald", hue: 158, saturation: 67, lightness: 58, color: "#4ed49e", core: "#e7fff5", rim: "#145f4b", atmosphere: "rgba(50,211,154,.5)", ring: "rgba(166,244,214,.66)" }),
|
|
32
|
+
Object.freeze({ key: "ice", hue: 190, saturation: 88, lightness: 76, color: "#9beaff", core: "#f4fdff", rim: "#26627c", atmosphere: "rgba(116,224,255,.52)", ring: "rgba(206,246,255,.7)" }),
|
|
33
|
+
Object.freeze({ key: "copper", hue: 22, saturation: 74, lightness: 61, color: "#df8851", core: "#ffe8cf", rim: "#672b1c", atmosphere: "rgba(226,103,55,.48)", ring: "rgba(239,179,125,.66)" }),
|
|
34
|
+
Object.freeze({ key: "pearl", hue: 47, saturation: 31, lightness: 84, color: "#e7dfc7", core: "#ffffff", rim: "#6c6b78", atmosphere: "rgba(207,217,240,.46)", ring: "rgba(237,231,216,.72)" })
|
|
35
|
+
]);
|
|
36
|
+
const GALAXY_CELESTIAL_TYPES = Object.freeze({
|
|
37
|
+
core: Object.freeze(["star", "star", "gas-giant", "ringed"]),
|
|
38
|
+
active: Object.freeze(["gas-giant", "ringed", "ocean", "ice", "volcanic"]),
|
|
39
|
+
outer: Object.freeze(["rocky", "ocean", "ice", "volcanic", "dwarf", "ringed"])
|
|
40
|
+
});
|
|
24
41
|
export const GALAXY_ROTATION_RADIANS_PER_MS = 0.000012;
|
|
25
42
|
export const GALAXY_LAYOUT_CONFIG = Object.freeze({
|
|
26
|
-
minimumRadius:
|
|
27
|
-
radialSpan:
|
|
28
|
-
repulsionStrength:
|
|
29
|
-
desiredEdgeLength:
|
|
43
|
+
minimumRadius: 220,
|
|
44
|
+
radialSpan: 830,
|
|
45
|
+
repulsionStrength: 9200,
|
|
46
|
+
desiredEdgeLength: 285
|
|
30
47
|
});
|
|
31
48
|
|
|
32
49
|
export function formatRelationshipLabel(edge, separator = " · ") {
|
|
@@ -37,6 +54,17 @@ export function formatRelationshipLabel(edge, separator = " · ") {
|
|
|
37
54
|
return [subtype, ...keywords].filter(Boolean).join(separator) || "关系";
|
|
38
55
|
}
|
|
39
56
|
|
|
57
|
+
export function formatRelationshipStatusNote(edge) {
|
|
58
|
+
const statuses = [];
|
|
59
|
+
if (String(edge?.confirmationStatus ?? "pending") === "pending") statuses.push("待确认");
|
|
60
|
+
if (String(edge?.category ?? "") === "uncertain") statuses.push("关系类型未确定");
|
|
61
|
+
return statuses.length ? `(${statuses.join(" · ")})` : "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function formatRelationshipDetailLabel(edge) {
|
|
65
|
+
return `${formatRelationshipLabel(edge)}${formatRelationshipStatusNote(edge)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
export function groupRelationshipDetailsByCharacterName(graph, nodeId) {
|
|
41
69
|
const groups = new Map();
|
|
42
70
|
for (const edge of graph.edges) {
|
|
@@ -56,21 +84,90 @@ export function getRelationshipEdgeSelection(graph, edgeId) {
|
|
|
56
84
|
if (!edge) return null;
|
|
57
85
|
return {
|
|
58
86
|
edgeId: edge.id,
|
|
87
|
+
directed: edge.directed,
|
|
59
88
|
endpointIds: [edge.source, edge.target],
|
|
60
89
|
endpointNames: [graph.nodeById.get(edge.source)?.name ?? "未知角色", graph.nodeById.get(edge.target)?.name ?? "未知角色"],
|
|
61
90
|
label: formatRelationshipLabel(edge)
|
|
62
91
|
};
|
|
63
92
|
}
|
|
64
93
|
|
|
94
|
+
export function assignRelationshipEdgeCurves(edges, gap = RELATIONSHIP_EDGE_GAP) {
|
|
95
|
+
const groups = new Map();
|
|
96
|
+
for (const edge of edges) {
|
|
97
|
+
const endpoints = [String(edge.source), String(edge.target)].sort((left, right) => left.localeCompare(right));
|
|
98
|
+
const key = endpoints.join("\u0000");
|
|
99
|
+
const group = groups.get(key) ?? [];
|
|
100
|
+
group.push(edge);
|
|
101
|
+
groups.set(key, group);
|
|
102
|
+
}
|
|
103
|
+
const offsets = new Map();
|
|
104
|
+
for (const group of groups.values()) {
|
|
105
|
+
const ordered = [...group].sort((left, right) => String(left.id).localeCompare(String(right.id)));
|
|
106
|
+
ordered.forEach((edge, index) => {
|
|
107
|
+
const canonicalOffset = (index - (ordered.length - 1) / 2) * gap;
|
|
108
|
+
const followsCanonicalDirection = String(edge.source).localeCompare(String(edge.target)) <= 0;
|
|
109
|
+
offsets.set(String(edge.id), canonicalOffset * (followsCanonicalDirection ? 1 : -1));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return offsets;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getRelationshipEdgeGeometry(from, to, sourceRadius = 0, targetRadius = 0, curveOffset = 0) {
|
|
116
|
+
const dx = Number(to?.x ?? 0) - Number(from?.x ?? 0);
|
|
117
|
+
const dy = Number(to?.y ?? 0) - Number(from?.y ?? 0);
|
|
118
|
+
const distance = Math.max(1, Math.hypot(dx, dy));
|
|
119
|
+
const normalX = -dy / distance;
|
|
120
|
+
const normalY = dx / distance;
|
|
121
|
+
const control = {
|
|
122
|
+
x: Number(from?.x ?? 0) + dx / 2 + normalX * curveOffset * 2,
|
|
123
|
+
y: Number(from?.y ?? 0) + dy / 2 + normalY * curveOffset * 2
|
|
124
|
+
};
|
|
125
|
+
const sourceTangentX = control.x - Number(from?.x ?? 0);
|
|
126
|
+
const sourceTangentY = control.y - Number(from?.y ?? 0);
|
|
127
|
+
const sourceTangentLength = Math.max(1, Math.hypot(sourceTangentX, sourceTangentY));
|
|
128
|
+
const targetTangentX = Number(to?.x ?? 0) - control.x;
|
|
129
|
+
const targetTangentY = Number(to?.y ?? 0) - control.y;
|
|
130
|
+
const targetTangentLength = Math.max(1, Math.hypot(targetTangentX, targetTangentY));
|
|
131
|
+
const startClearance = Math.max(0, Number(sourceRadius) || 0) + 2;
|
|
132
|
+
const endClearance = Math.max(0, Number(targetRadius) || 0) + 2;
|
|
133
|
+
const start = {
|
|
134
|
+
x: Number(from?.x ?? 0) + sourceTangentX / sourceTangentLength * startClearance,
|
|
135
|
+
y: Number(from?.y ?? 0) + sourceTangentY / sourceTangentLength * startClearance
|
|
136
|
+
};
|
|
137
|
+
const end = {
|
|
138
|
+
x: Number(to?.x ?? 0) - targetTangentX / targetTangentLength * endClearance,
|
|
139
|
+
y: Number(to?.y ?? 0) - targetTangentY / targetTangentLength * endClearance
|
|
140
|
+
};
|
|
141
|
+
const curved = Math.abs(curveOffset) > 0.01;
|
|
142
|
+
const labelPoint = curved
|
|
143
|
+
? {
|
|
144
|
+
x: (start.x + end.x) / 4 + control.x / 2,
|
|
145
|
+
y: (start.y + end.y) / 4 + control.y / 2
|
|
146
|
+
}
|
|
147
|
+
: { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
148
|
+
return {
|
|
149
|
+
path: curved
|
|
150
|
+
? `M ${start.x.toFixed(1)} ${start.y.toFixed(1)} Q ${control.x.toFixed(1)} ${control.y.toFixed(1)} ${end.x.toFixed(1)} ${end.y.toFixed(1)}`
|
|
151
|
+
: `M ${start.x.toFixed(1)} ${start.y.toFixed(1)} L ${end.x.toFixed(1)} ${end.y.toFixed(1)}`,
|
|
152
|
+
labelX: labelPoint.x,
|
|
153
|
+
labelY: labelPoint.y,
|
|
154
|
+
angle: Math.atan2(end.y - start.y, end.x - start.x) * 180 / Math.PI
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
65
158
|
export function resolveRelationshipNodeGroup(node) {
|
|
66
159
|
const organizations = Array.isArray(node?.organizations) ? node.organizations : [];
|
|
67
160
|
const orgName = organizations
|
|
68
161
|
.map((item) => String(item?.name ?? item ?? "").trim())
|
|
69
162
|
.find(Boolean);
|
|
70
163
|
const species = String(node?.species ?? "").trim();
|
|
164
|
+
const rootSpecies = String(node?.rootSpecies ?? "").trim();
|
|
71
165
|
const identity = String(node?.identity ?? "").trim();
|
|
72
166
|
if (orgName) return { type: "organization", key: `org:${orgName}`, label: orgName };
|
|
73
|
-
if (
|
|
167
|
+
if (rootSpecies || species) {
|
|
168
|
+
const groupSpecies = rootSpecies || species;
|
|
169
|
+
return { type: "species", key: `species:${groupSpecies}`, label: groupSpecies };
|
|
170
|
+
}
|
|
74
171
|
if (identity) return { type: "identity", key: `identity:${identity}`, label: identity };
|
|
75
172
|
return { type: "default", key: "default", label: "未分组" };
|
|
76
173
|
}
|
|
@@ -100,6 +197,15 @@ function hashString(value) {
|
|
|
100
197
|
return hash >>> 0;
|
|
101
198
|
}
|
|
102
199
|
|
|
200
|
+
function mixHash(value) {
|
|
201
|
+
let mixed = Number(value) >>> 0;
|
|
202
|
+
mixed ^= mixed >>> 16;
|
|
203
|
+
mixed = Math.imul(mixed, 0x7feb352d);
|
|
204
|
+
mixed ^= mixed >>> 15;
|
|
205
|
+
mixed = Math.imul(mixed, 0x846ca68b);
|
|
206
|
+
return (mixed ^ (mixed >>> 16)) >>> 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
103
209
|
function seededRandom(seed) {
|
|
104
210
|
let value = seed || 1;
|
|
105
211
|
return () => {
|
|
@@ -126,6 +232,7 @@ export function buildRelationshipGraph(characters, relationships) {
|
|
|
126
232
|
name: String(character.name),
|
|
127
233
|
aliases: Array.isArray(character.aliases) ? character.aliases : [],
|
|
128
234
|
species: String(character.species ?? ""),
|
|
235
|
+
rootSpecies: String(character.race?.lineage?.[0]?.name ?? character.species ?? ""),
|
|
129
236
|
identity: String(character.attributes?.identity ?? ""),
|
|
130
237
|
organizations,
|
|
131
238
|
locked: Array.isArray(character.lockedFields) && character.lockedFields.length > 0,
|
|
@@ -673,15 +780,41 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
|
|
|
673
780
|
svg.setAttribute("viewBox", `0 0 ${layout.width} ${layout.height}`);
|
|
674
781
|
svg.setAttribute("preserveAspectRatio", "none");
|
|
675
782
|
svg.setAttribute("aria-label", "人物关系连线");
|
|
783
|
+
const arrowMarkerId = `relationship-edge-arrow-${++relationshipRendererSequence}`;
|
|
784
|
+
const definitions = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
785
|
+
const arrowMarker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
786
|
+
arrowMarker.id = arrowMarkerId;
|
|
787
|
+
arrowMarker.setAttribute("viewBox", "0 0 8 8");
|
|
788
|
+
arrowMarker.setAttribute("markerWidth", "8");
|
|
789
|
+
arrowMarker.setAttribute("markerHeight", "8");
|
|
790
|
+
arrowMarker.setAttribute("refX", "7");
|
|
791
|
+
arrowMarker.setAttribute("refY", "4");
|
|
792
|
+
arrowMarker.setAttribute("markerUnits", "userSpaceOnUse");
|
|
793
|
+
arrowMarker.setAttribute("orient", "auto");
|
|
794
|
+
const arrowHead = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
795
|
+
arrowHead.classList.add("relationship-edge-arrow");
|
|
796
|
+
arrowHead.setAttribute("d", "M 0 0 L 8 4 L 0 8 Z");
|
|
797
|
+
arrowHead.setAttribute("fill", "context-stroke");
|
|
798
|
+
arrowMarker.append(arrowHead);
|
|
799
|
+
definitions.append(arrowMarker);
|
|
800
|
+
svg.append(definitions);
|
|
676
801
|
const edgeElements = [];
|
|
677
802
|
const edgeElementsByNode = new Map(graph.nodes.map((node) => [node.id, []]));
|
|
803
|
+
const edgeCurveOffsets = assignRelationshipEdgeCurves(graph.edges);
|
|
804
|
+
const nodeVisualRadii = new Map(graph.nodes.map((node) => [node.id, Math.max(4, Number(node.nodeSize) / 2 || 4)]));
|
|
678
805
|
const updateEdgeGeometry = ({ edge, hitPath, path }, { includeHit = true } = {}) => {
|
|
679
806
|
const from = positions.get(edge.source);
|
|
680
807
|
const to = positions.get(edge.target);
|
|
681
808
|
if (!from || !to) return;
|
|
682
|
-
const geometry =
|
|
683
|
-
|
|
684
|
-
|
|
809
|
+
const geometry = getRelationshipEdgeGeometry(
|
|
810
|
+
from,
|
|
811
|
+
to,
|
|
812
|
+
nodeVisualRadii.get(edge.source),
|
|
813
|
+
nodeVisualRadii.get(edge.target),
|
|
814
|
+
edgeCurveOffsets.get(edge.id)
|
|
815
|
+
);
|
|
816
|
+
path.setAttribute("d", geometry.path);
|
|
817
|
+
if (includeHit) hitPath.setAttribute("d", geometry.path);
|
|
685
818
|
};
|
|
686
819
|
const updateLabelGeometry = (edge) => {
|
|
687
820
|
const from = positions.get(edge.source);
|
|
@@ -690,9 +823,16 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
|
|
|
690
823
|
const dx = to.x - from.x;
|
|
691
824
|
const dy = to.y - from.y;
|
|
692
825
|
const distance = Math.max(1, Math.hypot(dx, dy));
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
826
|
+
const geometry = getRelationshipEdgeGeometry(
|
|
827
|
+
from,
|
|
828
|
+
to,
|
|
829
|
+
nodeVisualRadii.get(edge.source),
|
|
830
|
+
nodeVisualRadii.get(edge.target),
|
|
831
|
+
edgeCurveOffsets.get(edge.id)
|
|
832
|
+
);
|
|
833
|
+
const middleX = geometry.labelX;
|
|
834
|
+
const middleY = geometry.labelY - 4;
|
|
835
|
+
let angle = geometry.angle;
|
|
696
836
|
if (angle > 90 || angle < -90) angle += 180;
|
|
697
837
|
const fullLabel = label.dataset.fullLabel || label.textContent || "";
|
|
698
838
|
// 按连线长度截断,短边只显示极短摘要,完整内容在底部详情
|
|
@@ -719,6 +859,13 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
|
|
|
719
859
|
path.style.setProperty("--edge-opacity", "0.24");
|
|
720
860
|
path.style.setProperty("--edge-width", "1");
|
|
721
861
|
if (edge.confirmationStatus === "pending") path.classList.add("is-pending");
|
|
862
|
+
if (edge.directed) {
|
|
863
|
+
path.classList.add("is-directed");
|
|
864
|
+
path.setAttribute("marker-end", `url(#${arrowMarkerId})`);
|
|
865
|
+
}
|
|
866
|
+
const sourceName = graph.nodeById.get(edge.source)?.name ?? "未知角色";
|
|
867
|
+
const targetName = graph.nodeById.get(edge.target)?.name ?? "未知角色";
|
|
868
|
+
hitPath.setAttribute("aria-label", `选择 ${sourceName}${edge.directed ? " 指向 " : " 与 "}${targetName} 的关系:${formatRelationshipLabel(edge)}`);
|
|
722
869
|
svg.append(hitPath, path);
|
|
723
870
|
const edgeElement = { edge, hitPath, path };
|
|
724
871
|
edgeElements.push(edgeElement);
|
|
@@ -1023,11 +1170,12 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
|
|
|
1023
1170
|
label.dataset.fullLabel = fullLabel;
|
|
1024
1171
|
label.classList.remove("hidden");
|
|
1025
1172
|
updateLabelGeometry(edgeElement.edge);
|
|
1026
|
-
|
|
1173
|
+
const direction = selection.directed ? "→" : "↔";
|
|
1174
|
+
focusText.textContent = `关系:${selection.endpointNames[0]} ${direction} ${selection.endpointNames[1]}`;
|
|
1027
1175
|
const heading = document.createElement("b");
|
|
1028
|
-
heading.textContent = `${selection.endpointNames[0]}
|
|
1176
|
+
heading.textContent = `${selection.endpointNames[0]} ${direction} ${selection.endpointNames[1]}`;
|
|
1029
1177
|
const detailText = document.createElement("span");
|
|
1030
|
-
detailText.textContent =
|
|
1178
|
+
detailText.textContent = formatRelationshipDetailLabel(edgeElement.edge);
|
|
1031
1179
|
edgeDetail.replaceChildren(heading, detailText);
|
|
1032
1180
|
edgeDetail.classList.remove("hidden");
|
|
1033
1181
|
};
|
|
@@ -1348,17 +1496,19 @@ export function createGalaxyStarfield(seed, count = 3600) {
|
|
|
1348
1496
|
const stars = [];
|
|
1349
1497
|
const armCount = 4;
|
|
1350
1498
|
for (let index = 0; index < count; index += 1) {
|
|
1351
|
-
const radius = 55 + Math.pow(random(), 0.62) *
|
|
1499
|
+
const radius = 55 + Math.pow(random(), 0.62) * 1380;
|
|
1352
1500
|
const arm = index % armCount;
|
|
1353
1501
|
const armAngle = arm / armCount * Math.PI * 2;
|
|
1354
1502
|
const angle = armAngle + radius * 0.0065 + (random() - 0.5) * (0.42 + radius / 1100);
|
|
1355
1503
|
const thickness = 22 + radius * 0.105;
|
|
1504
|
+
const temperature = random();
|
|
1356
1505
|
stars.push({
|
|
1357
1506
|
x: Math.cos(angle) * radius + (random() - 0.5) * 62,
|
|
1358
1507
|
y: (random() + random() + random() - 1.5) * thickness,
|
|
1359
1508
|
z: Math.sin(angle) * radius + (random() - 0.5) * 62,
|
|
1360
1509
|
size: random() > 0.965 ? 1.7 + random() * 1.4 : 0.45 + random() * 0.85,
|
|
1361
|
-
brightness: 0.22 + random() * 0.78
|
|
1510
|
+
brightness: 0.22 + random() * 0.78,
|
|
1511
|
+
color: temperature < 0.2 ? "255,218,176" : temperature > 0.78 ? "174,211,255" : "226,237,255"
|
|
1362
1512
|
});
|
|
1363
1513
|
}
|
|
1364
1514
|
return stars;
|
|
@@ -1404,22 +1554,37 @@ export function getGalaxyNodeAppearance(node, maxDegree) {
|
|
|
1404
1554
|
const weightedDegree = Math.max(0, Number(node?.weightedDegree) || 0);
|
|
1405
1555
|
const confidenceBoost = clamp(weightedDegree / Math.max(1, degree) / 1.35, 0, 1);
|
|
1406
1556
|
const intensity = clamp(normalizedDegree * 0.8 + confidenceBoost * 0.2, 0, 1);
|
|
1407
|
-
const hue = Math.round(218 - intensity * 166);
|
|
1408
|
-
const saturation = Math.round(58 + intensity * 35);
|
|
1409
|
-
const lightness = Math.round(47 + intensity * 27);
|
|
1410
1557
|
const brightness = (0.7 + intensity * 0.68).toFixed(3);
|
|
1411
1558
|
const glow = (0.26 + intensity * 0.74).toFixed(3);
|
|
1412
1559
|
const tier = intensity >= 0.7 ? "core" : intensity >= 0.34 ? "active" : "outer";
|
|
1560
|
+
const appearanceSeed = mixHash(hashString([
|
|
1561
|
+
String(node?.id ?? ""),
|
|
1562
|
+
String(node?.name ?? ""),
|
|
1563
|
+
String(node?.groupKey ?? ""),
|
|
1564
|
+
String(node?.species ?? ""),
|
|
1565
|
+
String(node?.identity ?? "")
|
|
1566
|
+
].join("|")));
|
|
1567
|
+
const palette = GALAXY_CELESTIAL_PALETTES[appearanceSeed % GALAXY_CELESTIAL_PALETTES.length];
|
|
1568
|
+
const celestialTypes = GALAXY_CELESTIAL_TYPES[tier];
|
|
1569
|
+
const celestialType = celestialTypes[Math.floor(appearanceSeed / GALAXY_CELESTIAL_PALETTES.length) % celestialTypes.length];
|
|
1570
|
+
const sizeScale = ({ star: 1.18, "gas-giant": 1.12, ringed: 1.08, ocean: 1, ice: 0.96, volcanic: 1.02, rocky: 0.92, dwarf: 0.76 })[celestialType] ?? 1;
|
|
1413
1571
|
return {
|
|
1414
1572
|
degree,
|
|
1415
1573
|
intensity,
|
|
1416
|
-
hue,
|
|
1417
|
-
saturation,
|
|
1418
|
-
lightness,
|
|
1574
|
+
hue: palette.hue,
|
|
1575
|
+
saturation: palette.saturation,
|
|
1576
|
+
lightness: palette.lightness,
|
|
1419
1577
|
brightness,
|
|
1420
1578
|
glow,
|
|
1421
1579
|
tier,
|
|
1422
|
-
|
|
1580
|
+
palette: palette.key,
|
|
1581
|
+
celestialType,
|
|
1582
|
+
sizeScale,
|
|
1583
|
+
color: palette.color,
|
|
1584
|
+
coreColor: palette.core,
|
|
1585
|
+
rimColor: palette.rim,
|
|
1586
|
+
atmosphereColor: palette.atmosphere,
|
|
1587
|
+
ringColor: palette.ring
|
|
1423
1588
|
};
|
|
1424
1589
|
}
|
|
1425
1590
|
|
|
@@ -1427,6 +1592,10 @@ export function getGalaxyNodeMarkerCenterOffset(nodeSize) {
|
|
|
1427
1592
|
return 8 + Math.max(0, Number(nodeSize) || 0) / 2;
|
|
1428
1593
|
}
|
|
1429
1594
|
|
|
1595
|
+
export function getGalaxyNodeDepthOpacity(depth) {
|
|
1596
|
+
return clamp(1.28 - Math.max(0, Number(depth) || 0) / 4800, 0.72, 1);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1430
1599
|
export function distanceToGalaxyEdge(point, from, to) {
|
|
1431
1600
|
const deltaX = to.x - from.x;
|
|
1432
1601
|
const deltaY = to.y - from.y;
|
|
@@ -1457,7 +1626,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1457
1626
|
const layout = layoutGalaxy(graph, seed);
|
|
1458
1627
|
const stars = createGalaxyStarfield(`${seed}|stars`);
|
|
1459
1628
|
const initialNodePositions = new Map(layout.nodes.map((node) => [node.id, { x: node.x, y: node.y, z: node.z }]));
|
|
1460
|
-
const initialCamera = Object.freeze({ yaw: -0.38, pitch: 0.72, distance:
|
|
1629
|
+
const initialCamera = Object.freeze({ yaw: -0.38, pitch: 0.72, distance: 1560, focalRatio: 1.72, zoom: 1, targetX: 0, targetY: 0, targetZ: 0 });
|
|
1461
1630
|
const camera = { ...initialCamera };
|
|
1462
1631
|
const nodeElements = new Map();
|
|
1463
1632
|
const cleanups = [];
|
|
@@ -1556,7 +1725,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1556
1725
|
const radius = star.size * perspective;
|
|
1557
1726
|
const twinkle = 0.82 + Math.sin(index * 12.9898 + camera.yaw * 5) * 0.18;
|
|
1558
1727
|
const alpha = clamp(star.brightness * twinkle * perspective, 0.08, 0.92);
|
|
1559
|
-
context.fillStyle = `rgba(
|
|
1728
|
+
context.fillStyle = `rgba(${star.color},${alpha})`;
|
|
1560
1729
|
context.beginPath();
|
|
1561
1730
|
context.arc(point.x, point.y, Math.max(0.28, radius), 0, Math.PI * 2);
|
|
1562
1731
|
context.fill();
|
|
@@ -1662,7 +1831,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1662
1831
|
element.style.transformOrigin = `50% ${markerCenterOffset}px`;
|
|
1663
1832
|
element.style.transform = `translate3d(${point.x}px, ${point.y}px, 0) translate(-50%, -${markerCenterOffset}px) scale(${perspective * selectedScale})`;
|
|
1664
1833
|
element.style.zIndex = String(10000 - Math.round(point.depth));
|
|
1665
|
-
element.style.setProperty("--depth-opacity", String(
|
|
1834
|
+
element.style.setProperty("--depth-opacity", String(getGalaxyNodeDepthOpacity(point.depth)));
|
|
1666
1835
|
element.dataset.worldX = node.x.toFixed(2);
|
|
1667
1836
|
element.dataset.worldY = node.y.toFixed(2);
|
|
1668
1837
|
element.dataset.worldZ = node.z.toFixed(2);
|
|
@@ -1773,7 +1942,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1773
1942
|
category.className = edge.category;
|
|
1774
1943
|
return category;
|
|
1775
1944
|
});
|
|
1776
|
-
const labels = [...new Set(group.edges.map((edge) =>
|
|
1945
|
+
const labels = [...new Set(group.edges.map((edge) => formatRelationshipDetailLabel(edge)))];
|
|
1777
1946
|
item.append(...categories, document.createTextNode(`${group.name} · ${labels.join(";")}`));
|
|
1778
1947
|
list.append(item);
|
|
1779
1948
|
}
|
|
@@ -1786,11 +1955,11 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1786
1955
|
detail.classList.remove("hidden");
|
|
1787
1956
|
detail.replaceChildren();
|
|
1788
1957
|
const heading = document.createElement("strong");
|
|
1789
|
-
heading.textContent = selection.endpointNames.join(" ↔ ");
|
|
1958
|
+
heading.textContent = selection.endpointNames.join(selection.directed ? " → " : " ↔ ");
|
|
1790
1959
|
const category = document.createElement("small");
|
|
1791
1960
|
category.textContent = RELATION_STYLE[edge.category].label;
|
|
1792
1961
|
const description = document.createElement("p");
|
|
1793
|
-
description.textContent =
|
|
1962
|
+
description.textContent = formatRelationshipDetailLabel(edge);
|
|
1794
1963
|
detail.append(heading, category, description);
|
|
1795
1964
|
shell.dataset.selectedEdgeSource = selection.endpointIds[0];
|
|
1796
1965
|
shell.dataset.selectedEdgeTarget = selection.endpointIds[1];
|
|
@@ -1807,17 +1976,22 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
|
|
|
1807
1976
|
button.className = "galaxy-node";
|
|
1808
1977
|
button.dataset.galaxyNode = node.id;
|
|
1809
1978
|
button.dataset.relationshipTier = appearance.tier;
|
|
1810
|
-
|
|
1979
|
+
button.dataset.celestialType = appearance.celestialType;
|
|
1980
|
+
button.dataset.celestialPalette = appearance.palette;
|
|
1981
|
+
const nodeSize = clamp((10 + Math.sqrt(node.degree / maxDegree) * 28) * appearance.sizeScale, 8, 48);
|
|
1811
1982
|
button.style.setProperty("--node-size", `${nodeSize}px`);
|
|
1812
1983
|
button.dataset.nodeSize = nodeSize.toFixed(3);
|
|
1813
1984
|
button.style.setProperty("--node-color", appearance.color);
|
|
1985
|
+
button.style.setProperty("--node-core", appearance.coreColor);
|
|
1986
|
+
button.style.setProperty("--node-rim", appearance.rimColor);
|
|
1987
|
+
button.style.setProperty("--node-atmosphere", appearance.atmosphereColor);
|
|
1988
|
+
button.style.setProperty("--node-ring", appearance.ringColor);
|
|
1814
1989
|
button.style.setProperty("--node-brightness", appearance.brightness);
|
|
1815
1990
|
button.style.setProperty("--node-glow", appearance.glow);
|
|
1816
1991
|
const marker = document.createElement("i");
|
|
1817
1992
|
const label = document.createElement("span");
|
|
1818
1993
|
label.textContent = node.name;
|
|
1819
1994
|
button.append(marker, label);
|
|
1820
|
-
button.title = `${node.degree} 条关系 · ${appearance.tier === "core" ? "核心高亮" : appearance.tier === "active" ? "活跃连接" : "外围连接"}`;
|
|
1821
1995
|
button.setAttribute("aria-label", `${node.name},${node.degree} 条关系,${appearance.tier === "core" ? "核心高亮" : appearance.tier === "active" ? "活跃连接" : "外围连接"}${node.aliases.length ? `,别名 ${node.aliases.join("、")}` : ""}`);
|
|
1822
1996
|
button.setAttribute("aria-grabbed", "false");
|
|
1823
1997
|
let nodeDrag = null;
|
package/dist/public/styles.css
CHANGED
|
@@ -788,6 +788,19 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
788
788
|
.record-card small { color: var(--accent); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; }
|
|
789
789
|
.character-card { cursor: pointer; }
|
|
790
790
|
.character-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
791
|
+
.character-audit-panel { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 18px; padding: 15px 17px; border: 1px solid var(--line); background: var(--surface-soft); }
|
|
792
|
+
.character-audit-panel div { display: grid; gap: 5px; }
|
|
793
|
+
.character-audit-panel small { max-width: 760px; color: var(--muted); line-height: 1.55; }
|
|
794
|
+
.character-duplicate-review { grid-column: 1 / -1; }
|
|
795
|
+
.character-duplicate-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 14px 0; }
|
|
796
|
+
.character-duplicate-pair section { display: grid; align-content: start; gap: 8px; min-width: 0; padding: 13px; border: 1px solid var(--line); background: var(--surface-soft); }
|
|
797
|
+
.character-duplicate-pair section > small { color: var(--muted); }
|
|
798
|
+
.character-duplicate-pair section p { margin: 0; }
|
|
799
|
+
.character-duplicate-evidence { display: grid; gap: 8px; margin: 13px 0; padding: 0; list-style: none; }
|
|
800
|
+
.character-duplicate-evidence li { display: grid; gap: 5px; padding: 10px 12px; border-left: 2px solid var(--accent); background: var(--surface-soft); }
|
|
801
|
+
.character-duplicate-evidence q { color: var(--text); line-height: 1.55; }
|
|
802
|
+
.character-duplicate-evidence small, .review-resolution-note { color: var(--muted); }
|
|
803
|
+
.character-duplicate-actions { flex-wrap: wrap; }
|
|
791
804
|
.card-actions { display: flex; gap: 6px; margin-top: 15px; }
|
|
792
805
|
.card-actions button { border: 1px solid var(--line); background: transparent; border-radius: 3px; font-size: 10px; padding: 5px 8px; }
|
|
793
806
|
.card-actions .primary-button { border-color: var(--accent); background: var(--accent); color: #fff; padding: 8px 13px; font-size: 12px; }
|
|
@@ -802,6 +815,19 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
802
815
|
.pill { display: inline-block; padding: 3px 7px; margin: 0 4px 4px 0; border-radius: 12px; background: var(--paper-deep); color: var(--muted); font-size: 9px; }
|
|
803
816
|
.organization-links { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin: 8px 0 6px; }.organization-links b { color: var(--muted); font-size: 9px; font-weight: 500; }.organization-links .pill { margin: 0; }.organization-empty { color: var(--muted); font-size: 9px; }.organization-pill { color: var(--accent-dark); background: rgba(139,61,44,.1); }.organization-settings { margin: 10px 0 6px; }.organization-members { margin-top: 9px !important; }.relationship-keyword { white-space: nowrap; }
|
|
804
817
|
.race-settings { display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0 6px; }.race-settings .pill { margin: 0; }.race-members { margin-top: 9px !important; }
|
|
818
|
+
.race-tree { display: grid; gap: 12px; }
|
|
819
|
+
.race-tree-node { min-width: 0; }
|
|
820
|
+
.race-tree-node > summary { display: flex; align-items: center; gap: 10px; min-height: 38px; padding: 8px 12px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); cursor: pointer; }
|
|
821
|
+
.race-tree-node > summary::marker { color: var(--accent); }
|
|
822
|
+
.race-tree-node > summary span { font-size: 14px; font-weight: 600; }
|
|
823
|
+
.race-tree-node > summary small { margin-left: auto; color: var(--muted); }
|
|
824
|
+
.race-tree-branch { display: grid; gap: 10px; margin: 8px 0 0 14px; padding-left: 14px; border-left: 1px solid var(--line); }
|
|
825
|
+
.race-tree-children { display: grid; gap: 10px; }
|
|
826
|
+
.race-card { min-height: 0; }
|
|
827
|
+
.race-path { margin: 2px 0 10px; color: var(--accent); font-family: var(--font-latin), monospace; font-size: 11px; }
|
|
828
|
+
.race-settings .pill { display: inline-flex; align-items: center; gap: 5px; }
|
|
829
|
+
.race-settings .pill small { color: inherit; font-size: 8px; letter-spacing: 0; text-transform: none; opacity: .72; }
|
|
830
|
+
.race-settings .pill.inherited { border-style: dashed; }
|
|
805
831
|
.config-section { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line); }
|
|
806
832
|
.config-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
807
833
|
.platform-system-prompt-section { margin-bottom: 36px; }
|
|
@@ -955,6 +981,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
955
981
|
}
|
|
956
982
|
.relationship-network .mind-edge,
|
|
957
983
|
.relationship-network .obsidian-edge { stroke: rgba(255,255,255,.26); stroke-width: 1px; opacity: 1; transition: opacity .12s ease, stroke .12s ease; }
|
|
984
|
+
.relationship-network .mind-edge.is-directed { stroke: rgba(214,218,232,.5); }
|
|
985
|
+
.relationship-network .relationship-edge-arrow { pointer-events: none; }
|
|
958
986
|
.relationship-network .mind-edge.family,
|
|
959
987
|
.relationship-network .mind-edge.social,
|
|
960
988
|
.relationship-network .mind-edge.emotional,
|
|
@@ -1094,7 +1122,19 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1094
1122
|
.galaxy-shell.is-rotating-camera { cursor: grabbing; }.galaxy-background, .galaxy-graph, .galaxy-node-layer { position: absolute; inset: 0; width: 100%; height: 100%; }.galaxy-background { opacity: 1; transition: opacity .2s ease; }.galaxy-background.hidden-stars { opacity: .96; }
|
|
1095
1123
|
.galaxy-graph { z-index: 2; }.galaxy-node-layer { z-index: 3; pointer-events: none; }
|
|
1096
1124
|
.galaxy-node { --node-size: 12px; position: absolute; left: 0; top: 0; display: grid; grid-template-rows: var(--node-size) auto; justify-items: center; align-items: start; gap: 5px; padding: 8px; border: 0; background: transparent; color: #edf7ff; cursor: grab; touch-action: none; pointer-events: auto; transform-origin: center; opacity: var(--depth-opacity, 1); will-change: transform, opacity; }
|
|
1097
|
-
.galaxy-node i { width: var(--node-size); height: var(--node-size); border-radius: 50%; background: radial-gradient(circle at
|
|
1125
|
+
.galaxy-node i { position: relative; isolation: isolate; width: var(--node-size); height: var(--node-size); border-radius: 50%; background: radial-gradient(circle at 32% 27%, var(--node-core, #fff) 0 7%, color-mix(in srgb, var(--node-color) 62%, #fff) 18%, var(--node-color) 52%, var(--node-rim, #07101f) 100%); box-shadow: inset -3px -4px 7px color-mix(in srgb, var(--node-rim, #07101f) 74%, transparent), 0 0 4px rgba(255,255,255,.82), 0 0 calc(7px + var(--node-glow) * 11px) var(--node-color), 0 0 calc(15px + var(--node-glow) * 25px) var(--node-atmosphere, color-mix(in srgb, var(--node-color) 72%, transparent)); filter: brightness(var(--node-brightness)); transition: filter .18s ease, box-shadow .18s ease; }
|
|
1126
|
+
.galaxy-node i::before, .galaxy-node i::after { content: ""; position: absolute; pointer-events: none; }
|
|
1127
|
+
.galaxy-node i::before { z-index: -1; inset: -42%; border-radius: 50%; background: radial-gradient(circle, var(--node-atmosphere) 0, transparent 68%); opacity: .64; }
|
|
1128
|
+
.galaxy-node[data-celestial-type="star"] i { background: radial-gradient(circle at 38% 34%, #fff 0 10%, var(--node-core) 22%, var(--node-color) 53%, var(--node-rim) 100%); box-shadow: 0 0 5px #fff, 0 0 calc(10px + var(--node-glow) * 15px) var(--node-color), 0 0 calc(24px + var(--node-glow) * 34px) var(--node-atmosphere); }
|
|
1129
|
+
.galaxy-node[data-celestial-type="star"] i::before { inset: -72%; opacity: .92; background: radial-gradient(circle, var(--node-atmosphere) 0 12%, transparent 66%); }
|
|
1130
|
+
.galaxy-node[data-celestial-type="gas-giant"] i { background: linear-gradient(168deg, transparent 0 17%, color-mix(in srgb, var(--node-core) 78%, transparent) 18% 25%, transparent 26% 40%, color-mix(in srgb, var(--node-rim) 64%, transparent) 41% 50%, transparent 51% 68%, color-mix(in srgb, var(--node-core) 54%, transparent) 69% 75%, transparent 76%), radial-gradient(circle at 34% 28%, var(--node-core) 0 7%, var(--node-color) 42%, var(--node-rim) 100%); }
|
|
1131
|
+
.galaxy-node[data-celestial-type="ringed"] i::after { z-index: 2; left: 50%; top: 50%; width: 168%; height: 48%; border: 1px solid var(--node-ring); border-left-color: color-mix(in srgb, var(--node-ring) 28%, transparent); border-right-color: color-mix(in srgb, var(--node-ring) 88%, #fff); border-radius: 50%; box-shadow: 0 0 3px var(--node-atmosphere); transform: translate(-50%, -50%) rotate(-19deg); }
|
|
1132
|
+
.galaxy-node[data-celestial-type="ocean"] i { background: radial-gradient(ellipse at 67% 63%, color-mix(in srgb, var(--node-core) 48%, transparent) 0 10%, transparent 12%), radial-gradient(circle at 28% 24%, var(--node-core) 0 6%, var(--node-color) 38%, color-mix(in srgb, var(--node-color) 58%, #0b2f62) 72%, var(--node-rim) 100%); }
|
|
1133
|
+
.galaxy-node[data-celestial-type="ice"] i { background: conic-gradient(from 35deg at 48% 52%, var(--node-core), var(--node-color), color-mix(in srgb, var(--node-color) 58%, #fff), var(--node-rim), var(--node-core)); }
|
|
1134
|
+
.galaxy-node[data-celestial-type="volcanic"] i { background: radial-gradient(circle at 66% 61%, #ffd37a 0 3%, #ff7438 5%, transparent 9%), radial-gradient(circle at 37% 72%, #ff9b48 0 3%, transparent 8%), radial-gradient(circle at 30% 25%, var(--node-core) 0 5%, var(--node-color) 38%, var(--node-rim) 100%); }
|
|
1135
|
+
.galaxy-node[data-celestial-type="rocky"] i { background: radial-gradient(circle at 67% 31%, color-mix(in srgb, var(--node-rim) 74%, transparent) 0 8%, transparent 10%), radial-gradient(circle at 34% 68%, color-mix(in srgb, var(--node-rim) 58%, transparent) 0 11%, transparent 13%), radial-gradient(circle at 30% 26%, var(--node-core) 0 5%, var(--node-color) 45%, var(--node-rim) 100%); }
|
|
1136
|
+
.galaxy-node[data-celestial-type="dwarf"] i { box-shadow: inset -2px -3px 5px var(--node-rim), 0 0 3px rgba(255,255,255,.65), 0 0 calc(5px + var(--node-glow) * 8px) var(--node-atmosphere); }
|
|
1137
|
+
.galaxy-node span { opacity: 0; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 6px; border-radius: 4px; background: rgba(5,7,13,.68); font-size: 10px; text-align: center; text-shadow: 0 1px 3px #000; transition: opacity .14s ease; }
|
|
1098
1138
|
.galaxy-node.show-label span, .galaxy-node:hover span, .galaxy-node:focus-visible span, .galaxy-node.is-selected span, .galaxy-node.is-related span, .galaxy-node.is-edge-endpoint span { opacity: 1; }.galaxy-node.is-selected i { background: #fff4bd; box-shadow: 0 0 5px #fff, 0 0 18px #ffc86b, 0 0 42px rgba(255,173,66,.82); }.galaxy-node.is-related i { background: #d8f4ff; box-shadow: 0 0 4px #fff, 0 0 15px #438cff, 0 0 28px rgba(67,140,255,.56); }.galaxy-node.is-edge-endpoint i { background: #fff; box-shadow: 0 0 6px #fff, 0 0 22px #6fcaff, 0 0 48px rgba(67,140,255,.92); }.galaxy-node.is-dimmed { opacity: .16; }.galaxy-node.is-selected, .galaxy-node.is-related, .galaxy-node.is-edge-endpoint { z-index: 2; }
|
|
1099
1139
|
.galaxy-node.is-dragging { cursor: grabbing; z-index: 6; }
|
|
1100
1140
|
.galaxy-close { position: fixed; z-index: 8; top: 22px; right: 25px; width: 42px; height: 42px; border: 1px solid rgba(255,255,255,.22); border-radius: 50%; background: rgba(5,7,13,.62); color: #fff; font-size: 25px; }
|
|
@@ -1152,8 +1192,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1152
1192
|
.message-action-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
|
1153
1193
|
.message-body > :first-child { margin-top: 0; }.message-body > :last-child { margin-bottom: 0; }
|
|
1154
1194
|
.message-body p { margin: 0 0 10px; white-space: normal; }
|
|
1155
|
-
.message-body h1, .message-body h2, .message-body h3, .message-body h4 { margin: 14px 0 7px; color: inherit; line-height: 1.35; }
|
|
1156
|
-
.message-body h1 { font-size: 17px; }.message-body h2 { font-size: 15px; }.message-body h3, .message-body h4 { font-size: 13px; }
|
|
1195
|
+
.message-body h1, .message-body h2, .message-body h3, .message-body h4, .message-body h5, .message-body h6 { margin: 14px 0 7px; color: inherit; line-height: 1.35; }
|
|
1196
|
+
.message-body h1 { font-size: 17px; }.message-body h2 { font-size: 15px; }.message-body h3, .message-body h4 { font-size: 13px; }.message-body h5, .message-body h6 { font-size: 11px; }
|
|
1157
1197
|
.message-body ul, .message-body ol { margin: 7px 0 11px; padding-left: 21px; }.message-body li { margin: 4px 0; }
|
|
1158
1198
|
.message-body li.markdown-depth-1 { margin-left: 14px; }.message-body li.markdown-depth-2 { margin-left: 28px; }.message-body li.markdown-depth-3 { margin-left: 42px; }
|
|
1159
1199
|
.message-body blockquote { margin: 9px 0; padding: 6px 9px; border-left: 3px solid currentColor; background: rgba(255,255,255,.35); opacity: .82; }
|
|
@@ -1170,6 +1210,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1170
1210
|
.message-body .markdown-table-scroll .markdown-align-left { text-align: left; }.message-body .markdown-table-scroll .markdown-align-center { text-align: center; }.message-body .markdown-table-scroll .markdown-align-right { text-align: right; }
|
|
1171
1211
|
.message-body a { color: var(--accent-dark); text-decoration: underline; text-underline-offset: 2px; }.user-message .message-body a { color: inherit; }
|
|
1172
1212
|
.message-body hr { margin: 12px 0; border: 0; border-top: 1px solid currentColor; opacity: .22; }
|
|
1213
|
+
.message-body .markdown-image { display: grid; justify-items: center; gap: 6px; margin: 14px 0; }
|
|
1214
|
+
.message-body .markdown-image img { display: block; max-width: 100%; height: auto; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
|
|
1215
|
+
.message-body .markdown-image small { color: var(--muted); font-size: 9px; line-height: 1.45; text-align: center; }
|
|
1173
1216
|
.message-meta { margin-top: 8px; color: var(--muted); font-size: 9px; }
|
|
1174
1217
|
.ai-process-details { margin: 0 0 10px; overflow: hidden; border: 1px solid var(--line); border-radius: 5px; background: color-mix(in srgb, var(--surface-soft) 82%, transparent); }
|
|
1175
1218
|
.ai-process-details > summary { display: flex; align-items: center; justify-content: space-between; gap: 9px; padding: 8px 10px; color: var(--muted); cursor: pointer; font-size: 10px; list-style: none; }
|
|
@@ -1390,6 +1433,45 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1390
1433
|
.character-editor-empty-field { display: grid; align-content: start; gap: 7px; color: var(--muted); font-size: 11px; }
|
|
1391
1434
|
.character-editor-empty-field span { padding: 11px; border: 1px dashed var(--line); border-radius: 4px; font-size: 10px; }
|
|
1392
1435
|
.character-editor-field-help { margin: -8px 0 0; color: var(--muted); font-size: 9px; }
|
|
1436
|
+
.character-markdown-sections { display: grid; grid-column: 1 / -1; gap: 14px; min-width: 0; }
|
|
1437
|
+
.character-markdown-list-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
1438
|
+
.character-markdown-list-toolbar > div { display: grid; gap: 3px; }
|
|
1439
|
+
.character-markdown-list-toolbar b { color: var(--ink); font-size: 12px; }
|
|
1440
|
+
.character-markdown-list-toolbar span, .character-markdown-toolbar span { color: var(--muted); font-size: 9px; }
|
|
1441
|
+
.character-markdown-section { min-width: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); }
|
|
1442
|
+
.character-markdown-section > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 15px 17px; border-bottom: 1px solid var(--line); background: var(--surface-soft); }
|
|
1443
|
+
.character-markdown-section > header > div:first-child { display: grid; gap: 4px; min-width: 0; }
|
|
1444
|
+
.character-markdown-section > header span { color: var(--accent-dark); font-size: 9px; letter-spacing: .08em; }
|
|
1445
|
+
.character-markdown-section > header h4 { margin: 0; font-size: 16px; }
|
|
1446
|
+
.character-markdown-section > header p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1447
|
+
.character-markdown-section > header > div:last-child { display: flex; flex: 0 0 auto; gap: 5px; }
|
|
1448
|
+
.character-markdown-section > header button, .character-markdown-version-list button { padding: 5px 8px; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font-size: 9px; }
|
|
1449
|
+
.character-markdown-document { min-width: 0; padding: 20px clamp(18px, 3vw, 34px); color: var(--ink); font-size: 13px; line-height: 1.75; overflow-wrap: anywhere; }
|
|
1450
|
+
.character-markdown-document h1, .character-markdown-document h2, .character-markdown-document h3, .character-markdown-document h4, .character-markdown-document h5, .character-markdown-document h6 { color: var(--ink); }
|
|
1451
|
+
.character-markdown-document blockquote { background: var(--paper-deep); }
|
|
1452
|
+
.character-markdown-empty, .character-markdown-status { margin: 0; padding: 18px; border: 1px dashed var(--line); border-radius: 5px; color: var(--muted); font-size: 10px; text-align: center; }
|
|
1453
|
+
.character-markdown-editor { display: grid; gap: 16px; padding: 17px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
|
|
1454
|
+
.character-markdown-editor-meta { display: grid; grid-template-columns: minmax(150px, .6fr) minmax(220px, 1.4fr); gap: 12px; }
|
|
1455
|
+
.character-markdown-editor label { display: grid; gap: 6px; color: var(--muted); font-size: 10px; }
|
|
1456
|
+
.character-markdown-editor input, .character-markdown-editor select, .character-markdown-editor textarea { width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 12px; }
|
|
1457
|
+
.character-markdown-summary-field { grid-column: 1 / -1; }
|
|
1458
|
+
.character-markdown-summary-field textarea { min-height: 72px; }
|
|
1459
|
+
.character-markdown-toolbar { display: flex; align-items: center; gap: 10px; }
|
|
1460
|
+
.character-markdown-toolbar .ghost-button { display: inline-flex; width: auto; padding: 7px 10px; border: 1px solid var(--line); border-radius: 4px; color: var(--ink); cursor: pointer; }
|
|
1461
|
+
.character-markdown-compose { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; min-height: 430px; }
|
|
1462
|
+
.character-markdown-compose > label, .character-markdown-compose > div { min-width: 0; }
|
|
1463
|
+
.character-markdown-compose textarea { height: 100%; min-height: 430px; resize: vertical; font-family: var(--font-latin), var(--font-cjk), monospace; line-height: 1.65; tab-size: 2; }
|
|
1464
|
+
.character-markdown-compose > div { display: grid; grid-template-rows: auto minmax(0, 1fr); overflow: hidden; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); }
|
|
1465
|
+
.character-markdown-preview-label { padding: 8px 10px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: 10px; }
|
|
1466
|
+
.character-markdown-compose .character-markdown-document { max-height: 620px; overflow-y: auto; padding: 14px 16px; }
|
|
1467
|
+
.character-markdown-change-note { max-width: 620px; }
|
|
1468
|
+
.character-markdown-editor-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
1469
|
+
.character-markdown-version-list { display: grid; gap: 7px; padding: 10px 16px 16px; border-top: 1px solid var(--line); background: var(--surface-soft); }
|
|
1470
|
+
.character-markdown-version-list article { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 12px; padding: 9px 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); }
|
|
1471
|
+
.character-markdown-version-list article > div { display: flex; gap: 8px; align-items: center; }
|
|
1472
|
+
.character-markdown-version-list time, .character-markdown-version-list p { color: var(--muted); font-size: 9px; }
|
|
1473
|
+
.character-markdown-version-list p { grid-column: 1; margin: 0; }
|
|
1474
|
+
.character-markdown-version-list button { grid-column: 2; grid-row: 1 / 3; align-self: center; }
|
|
1393
1475
|
.character-history-panel { min-width: 0; overflow-y: auto; border-left: 1px solid var(--line); background: var(--surface-soft); }
|
|
1394
1476
|
.character-history-heading { position: sticky; z-index: 2; top: 0; display: flex; align-items: flex-start; justify-content: space-between; padding: 18px 16px 14px; border-bottom: 1px solid var(--line); background: var(--panel); }
|
|
1395
1477
|
.character-history-heading h3 { margin: 2px 0 0; font-size: 16px; }
|
|
@@ -1475,6 +1557,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1475
1557
|
.character-editor-section-fields > label, .character-editor-section-fields > .form-field, .character-editor-field-help { grid-column: 1; }
|
|
1476
1558
|
.character-editor-section > header { align-items: flex-start; flex-direction: column; gap: 7px; }
|
|
1477
1559
|
.character-editor-section > header p { text-align: left; }
|
|
1560
|
+
.character-markdown-compose { grid-template-columns: minmax(0, 1fr); }
|
|
1561
|
+
.character-markdown-editor-meta { grid-template-columns: minmax(0, 1fr); }
|
|
1562
|
+
.character-markdown-summary-field { grid-column: 1; }
|
|
1478
1563
|
.character-editor-actions { grid-template-columns: minmax(0, 1fr); gap: 10px; }
|
|
1479
1564
|
.character-editor-actions > div { justify-content: flex-end; }
|
|
1480
1565
|
.character-editor-workspace.history-open .character-history-panel { width: min(360px, 86vw); }
|
package/dist/server-runtime.js
CHANGED
|
@@ -13,6 +13,7 @@ export async function startLocalServer(options) {
|
|
|
13
13
|
security = resolveRuntimeSecurity(options.env);
|
|
14
14
|
runtime = createRuntime({
|
|
15
15
|
databasePath: options.databasePath,
|
|
16
|
+
attachmentDirectory: join(options.dataDirectory, "attachments"),
|
|
16
17
|
masterSecret: loadMasterSecret(join(options.dataDirectory, "master.key"), options.env.AI_NOVEL_MASTER_KEY),
|
|
17
18
|
publicPath,
|
|
18
19
|
security
|