@macrostrat/feedback-components 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/esm/feedback-components.3b3a5357.js +137 -0
  3. package/dist/esm/feedback-components.3b3a5357.js.map +1 -0
  4. package/dist/esm/feedback-components.46a7a347.js +269 -0
  5. package/dist/esm/feedback-components.46a7a347.js.map +1 -0
  6. package/dist/esm/{node.28634e40.js → feedback-components.5509fab3.js} +11 -6
  7. package/dist/esm/feedback-components.5509fab3.js.map +1 -0
  8. package/dist/esm/feedback-components.586103e8.js +578 -0
  9. package/dist/esm/feedback-components.586103e8.js.map +1 -0
  10. package/dist/esm/{extractions.65bb73cc.js → feedback-components.5df2a926.js} +46 -18
  11. package/dist/esm/feedback-components.5df2a926.js.map +1 -0
  12. package/dist/esm/{main.module.cd706d67.js → feedback-components.6d32ee91.js} +1 -1
  13. package/dist/esm/{main.module.cd706d67.js.map → feedback-components.6d32ee91.js.map} +1 -1
  14. package/dist/esm/feedback-components.95dbe7d7.js +82 -0
  15. package/dist/esm/feedback-components.95dbe7d7.js.map +1 -0
  16. package/dist/esm/{type-selector.6e8952d6.js → feedback-components.ad9f284e.js} +6 -5
  17. package/dist/esm/feedback-components.ad9f284e.js.map +1 -0
  18. package/dist/esm/{main.module.8d366b6e.css → feedback-components.bf93773c.css} +1 -1
  19. package/dist/esm/{main.module.8d366b6e.css.map → feedback-components.bf93773c.css.map} +1 -1
  20. package/dist/esm/{main.module.2f2972c8.css → feedback-components.e273ed5b.css} +1 -1
  21. package/dist/esm/{main.module.2f2972c8.css.map → feedback-components.e273ed5b.css.map} +1 -1
  22. package/dist/esm/{main.module.d2fbdf09.js → feedback-components.f9850d85.js} +1 -1
  23. package/dist/esm/{main.module.d2fbdf09.js.map → feedback-components.f9850d85.js.map} +1 -1
  24. package/dist/esm/{edit-state.c39d8466.js → feedback-components.fa1d3641.js} +125 -7
  25. package/dist/esm/feedback-components.fa1d3641.js.map +1 -0
  26. package/dist/esm/feedback-components.fb60c70d.css +180 -0
  27. package/dist/esm/feedback-components.fb60c70d.css.map +1 -0
  28. package/dist/esm/index.d.ts +10 -64
  29. package/dist/esm/index.d.ts.map +1 -1
  30. package/dist/esm/index.js +2 -2
  31. package/dist/node/feedback-components.2f391fa4.js +2 -0
  32. package/dist/node/feedback-components.2f391fa4.js.map +1 -0
  33. package/dist/node/feedback-components.561466ac.js +2 -0
  34. package/dist/node/feedback-components.561466ac.js.map +1 -0
  35. package/dist/node/feedback-components.571ee23c.js +2 -0
  36. package/dist/node/feedback-components.571ee23c.js.map +1 -0
  37. package/dist/node/{main.module.ebdf985b.js → feedback-components.794f429b.js} +2 -2
  38. package/dist/node/feedback-components.794f429b.js.map +1 -0
  39. package/dist/node/{main.module.1fdfe813.css → feedback-components.83c21466.css} +1 -1
  40. package/dist/node/feedback-components.83c21466.css.map +1 -0
  41. package/dist/node/feedback-components.8b03e8be.js +2 -0
  42. package/dist/node/feedback-components.8b03e8be.js.map +1 -0
  43. package/dist/node/{main.module.6bc7d51b.css → feedback-components.9eb1d41a.css} +1 -1
  44. package/dist/node/feedback-components.9eb1d41a.css.map +1 -0
  45. package/dist/node/feedback-components.a39f7653.js +2 -0
  46. package/dist/node/feedback-components.a39f7653.js.map +1 -0
  47. package/dist/node/feedback-components.acac789b.js +2 -0
  48. package/dist/node/feedback-components.acac789b.js.map +1 -0
  49. package/dist/node/feedback-components.b7946db4.js +2 -0
  50. package/dist/node/feedback-components.b7946db4.js.map +1 -0
  51. package/dist/node/feedback-components.c459cc27.js +2 -0
  52. package/dist/node/feedback-components.c459cc27.js.map +1 -0
  53. package/dist/node/feedback-components.c88cb37f.css +2 -0
  54. package/dist/node/feedback-components.c88cb37f.css.map +1 -0
  55. package/dist/node/feedback-components.ec54a1e7.js +2 -0
  56. package/dist/node/feedback-components.ec54a1e7.js.map +1 -0
  57. package/dist/node/index.js +1 -1
  58. package/dist/node/index.js.map +1 -1
  59. package/package.json +7 -6
  60. package/src/extractions/index.ts +76 -21
  61. package/src/extractions/types.ts +6 -1
  62. package/src/feedback/edit-state.ts +184 -16
  63. package/src/feedback/feedback.module.sass +121 -9
  64. package/src/feedback/graph.ts +90 -32
  65. package/src/feedback/index.ts +553 -146
  66. package/src/feedback/node.ts +7 -1
  67. package/src/feedback/text-visualizer.ts +286 -49
  68. package/src/feedback/type-selector/index.ts +4 -2
  69. package/dist/esm/edit-state.c39d8466.js.map +0 -1
  70. package/dist/esm/extractions.65bb73cc.js.map +0 -1
  71. package/dist/esm/feedback.5c86878e.js +0 -252
  72. package/dist/esm/feedback.5c86878e.js.map +0 -1
  73. package/dist/esm/feedback.module.55921afe.css +0 -44
  74. package/dist/esm/feedback.module.55921afe.css.map +0 -1
  75. package/dist/esm/feedback.module.765b1e58.js +0 -28
  76. package/dist/esm/feedback.module.765b1e58.js.map +0 -1
  77. package/dist/esm/graph.f4f65d79.js +0 -83
  78. package/dist/esm/graph.f4f65d79.js.map +0 -1
  79. package/dist/esm/node.28634e40.js.map +0 -1
  80. package/dist/esm/text-visualizer.198e27ff.js +0 -101
  81. package/dist/esm/text-visualizer.198e27ff.js.map +0 -1
  82. package/dist/esm/type-selector.6e8952d6.js.map +0 -1
  83. package/dist/node/edit-state.f50ca728.js +0 -2
  84. package/dist/node/edit-state.f50ca728.js.map +0 -1
  85. package/dist/node/extractions.e6ea2eb9.js +0 -2
  86. package/dist/node/extractions.e6ea2eb9.js.map +0 -1
  87. package/dist/node/feedback.8d3d1219.js +0 -2
  88. package/dist/node/feedback.8d3d1219.js.map +0 -1
  89. package/dist/node/feedback.module.a8744203.js +0 -2
  90. package/dist/node/feedback.module.a8744203.js.map +0 -1
  91. package/dist/node/feedback.module.c4eab97d.css +0 -2
  92. package/dist/node/feedback.module.c4eab97d.css.map +0 -1
  93. package/dist/node/graph.ca5b649f.js +0 -2
  94. package/dist/node/graph.ca5b649f.js.map +0 -1
  95. package/dist/node/main.module.1857be22.js +0 -2
  96. package/dist/node/main.module.1857be22.js.map +0 -1
  97. package/dist/node/main.module.1fdfe813.css.map +0 -1
  98. package/dist/node/main.module.6bc7d51b.css.map +0 -1
  99. package/dist/node/main.module.ebdf985b.js.map +0 -1
  100. package/dist/node/node.33108ccc.js +0 -2
  101. package/dist/node/node.33108ccc.js.map +0 -1
  102. package/dist/node/text-visualizer.1e770afa.js +0 -2
  103. package/dist/node/text-visualizer.1e770afa.js.map +0 -1
  104. package/dist/node/type-selector.0035ef7d.js +0 -2
  105. package/dist/node/type-selector.0035ef7d.js.map +0 -1
@@ -5,6 +5,7 @@ import type { Entity, EntityExt, Highlight, EntityType } from "./types";
5
5
  import { CSSProperties } from "react";
6
6
  import { asChromaColor } from "@macrostrat/color-utils";
7
7
  import hyper from "@macrostrat/hyper";
8
+ import { useDarkMode } from "@macrostrat/ui-components";
8
9
 
9
10
  export type { Entity, EntityExt };
10
11
 
@@ -12,7 +13,7 @@ const h = hyper.styled(styles);
12
13
 
13
14
  export function buildHighlights(
14
15
  entities: EntityExt[],
15
- parent: EntityExt | null
16
+ parent: EntityExt | null,
16
17
  ): Highlight[] {
17
18
  let highlights = [];
18
19
  let parents = [];
@@ -25,8 +26,8 @@ export function buildHighlights(
25
26
  start: entity.indices[0],
26
27
  end: entity.indices[1],
27
28
  text: entity.name,
28
- backgroundColor: entity.type.color ?? "#ddd",
29
- tag: entity.type.name,
29
+ backgroundColor: entity.type?.color,
30
+ tag: entity.type?.name ?? "lith",
30
31
  id: entity.id,
31
32
  parents,
32
33
  });
@@ -40,17 +41,21 @@ export function enhanceData(extractionData, models, entityTypes) {
40
41
  ...extractionData,
41
42
  model: models.get(extractionData.model_id),
42
43
  entities: extractionData.entities?.map((d) =>
43
- enhanceEntity(d, entityTypes)
44
+ enhanceEntity(d, entityTypes),
44
45
  ),
45
46
  };
46
47
  }
47
48
 
48
49
  export function getTagStyle(
49
50
  baseColor: string,
50
- options: { highlighted?: boolean; inDarkMode?: boolean; active?: boolean }
51
+ options: { highlighted?: boolean; inDarkMode?: boolean; active?: boolean },
51
52
  ): CSSProperties {
52
- const _baseColor = asChromaColor(baseColor ?? "#ddd");
53
- const { highlighted = true, inDarkMode = false, active = false } = options;
53
+ const _baseColor = asChromaColor(baseColor ?? "#fff");
54
+ const {
55
+ highlighted = true,
56
+ inDarkMode = useDarkMode().isEnabled,
57
+ active = false,
58
+ } = options;
54
59
 
55
60
  let mixAmount = highlighted ? 0.8 : 0.5;
56
61
  let backgroundAlpha = highlighted ? 0.8 : 0.2;
@@ -60,27 +65,38 @@ export function getTagStyle(
60
65
  backgroundAlpha = 1;
61
66
  }
62
67
 
63
- const mixTarget = inDarkMode ? "white" : "black";
68
+ const mixTarget = "black";
64
69
 
65
- const color = _baseColor.mix(mixTarget, mixAmount).css();
70
+ const color = active ? "#000" : _baseColor.mix(mixTarget, mixAmount).hex();
66
71
  const borderColor = highlighted
67
- ? _baseColor.mix(mixTarget, mixAmount / 2).css()
72
+ ? _baseColor.mix(mixTarget, mixAmount / 1.1).hex()
68
73
  : "transparent";
69
74
 
75
+ let backgroundColor = active
76
+ ? _baseColor.alpha(backgroundAlpha).hex()
77
+ : normalizeColor(_baseColor.alpha(backgroundAlpha).hex());
78
+
79
+ // handle white backgrounds in light mode
80
+ if (!inDarkMode && backgroundColor === "#ffffff") {
81
+ console.log("Adjusting background color for light mode:", backgroundColor);
82
+ backgroundColor = "#f0f0f0";
83
+ }
84
+
70
85
  return {
71
86
  color,
72
- backgroundColor: _baseColor.alpha(backgroundAlpha).css(),
87
+ backgroundColor,
73
88
  boxSizing: "border-box",
74
89
  borderStyle: "solid",
75
90
  borderColor,
76
- borderWidth: "1px",
91
+ borderWidth: "1.5px",
77
92
  fontWeight: active ? "bold" : "normal",
93
+ fontSize: "0.9em",
78
94
  };
79
95
  }
80
96
 
81
97
  function enhanceEntity(
82
98
  entity: Entity,
83
- entityTypes: Map<number, EntityType>
99
+ entityTypes: Map<number, EntityType>,
84
100
  ): EntityExt {
85
101
  return {
86
102
  ...entity,
@@ -90,8 +106,8 @@ function enhanceEntity(
90
106
  }
91
107
 
92
108
  function addColor(entityType: EntityType, match = false) {
93
- const color = asChromaColor(entityType.color ?? "#ddd").brighten(
94
- match ? 1 : 2
109
+ const color = asChromaColor(entityType.color ?? "#fff").brighten(
110
+ match ? 1 : 2,
95
111
  );
96
112
 
97
113
  return { ...entityType, color: color.css() };
@@ -113,7 +129,7 @@ export function ExtractionContext({
113
129
  h(ModelInfo, { data: data.model }),
114
130
  h(
115
131
  "ul.entities",
116
- data.entities.map((d) => h(ExtractionInfo, { data: d, matchComponent }))
132
+ data.entities.map((d) => h(ExtractionInfo, { data: d, matchComponent })),
117
133
  ),
118
134
  ]);
119
135
  }
@@ -140,15 +156,16 @@ export function EntityTag({
140
156
  matchComponent = null,
141
157
  }: EntityTagProps) {
142
158
  const { name, type, match } = data;
159
+
143
160
  const className = classNames(
144
161
  {
145
162
  matched: match != null,
146
- type: data.type.name,
163
+ type: data.type?.name ?? "lith",
147
164
  },
148
- "entity"
165
+ "entity",
149
166
  );
150
167
 
151
- const style = getTagStyle(type.color ?? "#aaaaaa", { highlighted, active });
168
+ const style = getTagStyle(type?.color, { highlighted, active });
152
169
 
153
170
  let _matchLink = null;
154
171
  if (match != null && matchComponent != null) {
@@ -168,7 +185,7 @@ export function EntityTag({
168
185
  }
169
186
  },
170
187
  },
171
- [type.name, _matchLink]
188
+ [type?.name, _matchLink],
172
189
  ),
173
190
  ]);
174
191
  }
@@ -187,7 +204,7 @@ function ExtractionInfo({
187
204
  h.if(children.length > 0)([
188
205
  h(
189
206
  "ul.children",
190
- children.map((d) => h(ExtractionInfo, { data: d, matchComponent }))
207
+ children.map((d) => h(ExtractionInfo, { data: d, matchComponent })),
191
208
  ),
192
209
  ]),
193
210
  ]);
@@ -217,3 +234,41 @@ function HighlightedText(props: { text: string; highlights: Highlight[] }) {
217
234
  parts.push(text.slice(start));
218
235
  return h("span", parts);
219
236
  }
237
+
238
+ function normalizeColor(hex8) {
239
+ const background = useDarkMode().isEnabled ? "#000000" : "#ffffff";
240
+
241
+ const r = parseInt(hex8.slice(1, 3), 16);
242
+ const g = parseInt(hex8.slice(3, 5), 16);
243
+ const b = parseInt(hex8.slice(5, 7), 16);
244
+ const a = parseInt(hex8.slice(7, 9), 16) / 255;
245
+
246
+ const bgR = parseInt(background.slice(1, 3), 16);
247
+ const bgG = parseInt(background.slice(3, 5), 16);
248
+ const bgB = parseInt(background.slice(5, 7), 16);
249
+
250
+ const blend = (fg, bg) => Math.round((1 - a) * bg + a * fg);
251
+
252
+ const blendedR = blend(r, bgR);
253
+ const blendedG = blend(g, bgG);
254
+ const blendedB = blend(b, bgB);
255
+
256
+ return (
257
+ "#" +
258
+ blendedR.toString(16).padStart(2, "0") +
259
+ blendedG.toString(16).padStart(2, "0") +
260
+ blendedB.toString(16).padStart(2, "0")
261
+ );
262
+ }
263
+
264
+ function isHighlighted(id: number, selectedNodes: number[], nodes: any[]) {
265
+ if (selectedNodes?.length === 0) return true;
266
+ return (
267
+ selectedNodes?.includes(id) ||
268
+ nodes?.some(
269
+ (node) =>
270
+ selectedNodes?.includes(node.id) &&
271
+ node.children.some((child) => child.id === id),
272
+ )
273
+ );
274
+ }
@@ -1,4 +1,9 @@
1
- type EntityType = { name: string; color: string; id: number };
1
+ type EntityType = {
2
+ name: string;
3
+ color: string;
4
+ id: number;
5
+ description?: string;
6
+ };
2
7
  type Match = any;
3
8
 
4
9
  export interface Entity {
@@ -38,15 +38,26 @@ type TreeAction =
38
38
  | { type: "select-entity-type"; payload: EntityType }
39
39
  | { type: "toggle-entity-type-selector"; payload?: boolean | null }
40
40
  | { type: "deselect" }
41
- | { type: "reset" };
41
+ | { type: "reset" }
42
+ | { type: "delete-entity-type"; payload: { id: number } }
43
+ | {
44
+ type: "add-entity-type";
45
+ payload: { name: string; description: string; color: string };
46
+ }
47
+ | {
48
+ type: "update-entity-type";
49
+ payload: { id: number; name: string; description: string; color: string };
50
+ }
51
+ | { type: "select-range"; payload: { ids: number[] } };
42
52
 
43
53
  export type TreeDispatch = Dispatch<TreeAction>;
44
54
 
45
55
  export function useUpdatableTree(
46
56
  initialTree: TreeData[],
47
- entityTypes: Map<number, EntityType>
57
+ entityTypes: Map<number, EntityType>,
48
58
  ): [TreeState, TreeDispatch] {
49
59
  // Get the first entity type
60
+ // issue: grabs second entity instead of selected one
50
61
  const type = entityTypes.values().next().value;
51
62
 
52
63
  return useReducer(treeReducer, {
@@ -72,13 +83,84 @@ export function useTreeDispatch() {
72
83
  }
73
84
 
74
85
  function treeReducer(state: TreeState, action: TreeAction) {
75
- console.log(action);
76
86
  switch (action.type) {
87
+ case "add-entity-type": {
88
+ // Add a new entity type to the map
89
+ const { name, description, color } = action.payload;
90
+ const newId = state.lastInternalId - 1;
91
+ const newType: EntityType = {
92
+ id: newId,
93
+ name,
94
+ description: description === "" ? null : description,
95
+ color,
96
+ };
97
+
98
+ const newEntityTypesMap = new Map(state.entityTypesMap);
99
+ newEntityTypesMap.set(newId, newType);
100
+
101
+ return {
102
+ ...state,
103
+ entityTypesMap: newEntityTypesMap,
104
+ selectedEntityType: newType,
105
+ lastInternalId: newId,
106
+ };
107
+ }
108
+ case "update-entity-type": {
109
+ // Update an existing entity type in the map
110
+ const { id, name, description, color } = action.payload;
111
+ const newEntityTypesMap = new Map(state.entityTypesMap);
112
+ const oldType = newEntityTypesMap.get(id);
113
+
114
+ if (!oldType) {
115
+ console.warn(`Entity type with id ${id} not found`);
116
+ return state;
117
+ }
118
+
119
+ const updatedType: EntityType = {
120
+ ...oldType,
121
+ name,
122
+ description: description === "" ? null : description,
123
+ color,
124
+ };
125
+
126
+ newEntityTypesMap.set(id, updatedType);
127
+
128
+ // Update the tree to reflect the new type
129
+ const newTree = updateTreeTypes(state.tree, oldType, updatedType);
130
+
131
+ return {
132
+ ...state,
133
+ tree: newTree,
134
+ entityTypesMap: newEntityTypesMap,
135
+ selectedEntityType: updatedType,
136
+ };
137
+ }
138
+ case "select-range":
139
+ // Select a range of nodes by their IDs
140
+ const payloadIds = action.payload.ids;
141
+ const node1 = payloadIds[0];
142
+ const node2 = payloadIds[1];
143
+
144
+ // make list of nodes in order
145
+ const allNodes = flattenAndSort(state.tree);
146
+
147
+ // select all nodes between node1 and node2
148
+ const startIndex = allNodes.findIndex((node) => node.id === node1);
149
+ const endIndex = allNodes.findIndex((node) => node.id === node2);
150
+
151
+ const selectedNodes = allNodes.slice(startIndex, endIndex + 1);
152
+
153
+ console.log("Selecting range:", selectedNodes);
154
+ return {
155
+ ...state,
156
+ selectedNodes: selectedNodes.map((node) => node.id),
157
+ };
158
+
77
159
  case "move-node":
78
160
  // For each node in the tree, if the node is in the dragIds, remove it from the tree and collect it
79
161
  const [newTree, removedNodes] = removeNodes(
80
162
  state.tree,
81
- action.payload.dragIds
163
+ action.payload.dragIds,
82
164
  );
83
165
 
84
166
  let keyPath: (number | "children")[] = [];
@@ -97,7 +179,7 @@ function treeReducer(state: TreeState, action: TreeAction) {
97
179
  // For each node in the tree, if the node is in the ids, remove it from the tree
98
180
  const [newTree2, _removedNodes] = removeNodes(
99
181
  state.tree,
100
- action.payload.ids
182
+ action.payload.ids,
101
183
  );
102
184
  // Get children of the removed nodes
103
185
  // If children are not present elsewhere in the tree, insert them
@@ -112,21 +194,37 @@ function treeReducer(state: TreeState, action: TreeAction) {
112
194
  ...state,
113
195
  tree: [...newTree2, ...children],
114
196
  selectedNodes: state.selectedNodes.filter(
115
- (id) => !action.payload.ids.includes(id)
197
+ (id) => !action.payload.ids.includes(id),
116
198
  ),
117
199
  };
118
200
  case "select-node":
119
201
  const { ids } = action.payload;
120
- return { ...state, selectedNodes: ids };
202
+
203
+ const type =
204
+ action.payload.ids.length > 0
205
+ ? findNodeById(state.tree, ids[0])?.type
206
+ : null;
207
+
208
+ return { ...state, selectedNodes: ids, selectedEntityType: type };
121
209
  // otherwise fall through to toggle-node-selected for a single ID
122
210
  case "toggle-node-selected":
123
211
  const nodesToAdd = action.payload.ids.filter(
124
- (id) => !state.selectedNodes.includes(id)
212
+ (id) => !state.selectedNodes.includes(id),
125
213
  );
126
214
  const nodesToKeep = state.selectedNodes.filter(
127
- (id) => !action.payload.ids.includes(id)
215
+ (id) => !action.payload.ids.includes(id),
128
216
  );
129
- return { ...state, selectedNodes: [...nodesToKeep, ...nodesToAdd] };
217
+
218
+ const newType =
219
+ action.payload.ids.length > 0
220
+ ? findNodeById(state.tree, action.payload.ids[0])?.type
221
+ : null;
222
+
223
+ return {
224
+ ...state,
225
+ selectedNodes: [...nodesToKeep, ...nodesToAdd],
226
+ selectedEntityType: newType,
227
+ };
130
228
 
131
229
  case "create-node":
132
230
  const newId = state.lastInternalId - 1;
@@ -146,6 +244,25 @@ function treeReducer(state: TreeState, action: TreeAction) {
146
244
  lastInternalId: newId,
147
245
  };
148
246
 
247
+ case "delete-entity-type": {
248
+ // Remove the entity type from the map
249
+ console.log("Deleting entity type:", action.payload.id);
250
+ const { id } = action.payload;
251
+ const newEntityTypesMap = new Map(state.entityTypesMap);
252
+ const oldType = newEntityTypesMap.get(id);
253
+ newEntityTypesMap.delete(id);
254
+
255
+ const defaultType = newEntityTypesMap.values().next().value;
256
+ const newTree = updateTreeTypes(state.tree, oldType, defaultType);
257
+
258
+ return {
259
+ ...state,
260
+ tree: newTree,
261
+ entityTypesMap: newEntityTypesMap,
262
+ selectedNodes: [],
263
+ };
264
+ }
265
+
149
266
  /** Entity type selection */
150
267
  case "toggle-entity-type-selector":
151
268
  return {
@@ -197,7 +314,7 @@ function nodeIsInTree(tree: TreeData[], id: number): boolean {
197
314
 
198
315
  function buildNestedSpec(
199
316
  keyPath: (number | "children")[],
200
- innerSpec: Spec<any>
317
+ innerSpec: Spec<any>,
201
318
  ): Spec<TreeData[]> {
202
319
  // Build a nested object from a key path
203
320
 
@@ -211,7 +328,7 @@ function buildNestedSpec(
211
328
 
212
329
  function findNode(
213
330
  tree: TreeData[],
214
- id: number
331
+ id: number,
215
332
  ): (number | "children")[] | null {
216
333
  // Find the index of the node with the given id in the tree, returning the key path
217
334
  for (let i = 0; i < tree.length; i++) {
@@ -229,7 +346,7 @@ function findNode(
229
346
 
230
347
  function removeNodes(
231
348
  tree: TreeData[],
232
- ids: number[]
349
+ ids: number[],
233
350
  ): [TreeData[], TreeData[]] {
234
351
  /** Remove nodes with the given ids from the tree and return the new tree and the removed nodes */
235
352
  let newTree: TreeData[] = [];
@@ -259,6 +376,8 @@ export interface EntityOutput {
259
376
  name: string;
260
377
  match: any | null;
261
378
  reasoning: string | null;
379
+ color: string | null;
380
+ children: any[] | null;
262
381
  }
263
382
 
264
383
  export interface GraphData {
@@ -279,15 +398,17 @@ export function treeToGraph(tree: TreeData[]): GraphData {
279
398
  continue;
280
399
  }
281
400
 
282
- const { indices, id, name } = node;
401
+ const { indices, id, name, type, children } = node;
283
402
 
284
403
  const nodeData: EntityOutput = {
285
404
  id,
286
- type: node.type.id,
405
+ type: type.id,
406
+ color: type.color,
287
407
  name,
288
408
  txt_range: [indices],
289
409
  reasoning: null,
290
410
  match: node.match,
411
+ children,
291
412
  };
292
413
 
293
414
  nodeMap.set(node.id, node);
@@ -300,7 +421,7 @@ export function treeToGraph(tree: TreeData[]): GraphData {
300
421
 
301
422
  // Now process the children
302
423
  const { nodes: childNodes, edges: childEdges } = treeToGraph(
303
- node.children
424
+ node.children,
304
425
  );
305
426
  nodes.push(...childNodes);
306
427
  edges.push(...childEdges);
@@ -309,3 +430,50 @@ export function treeToGraph(tree: TreeData[]): GraphData {
309
430
 
310
431
  return { nodes, edges };
311
432
  }
433
+
434
+ function findNodeById(tree, id) {
435
+ for (const node of tree) {
436
+ if (node.id === id) {
437
+ return node;
438
+ }
439
+ if (node.children) {
440
+ const found = findNodeById(node.children, id);
441
+ if (found) return found;
442
+ }
443
+ }
444
+ return null;
445
+ }
446
+
447
+ function updateTreeTypes(tree, oldType, defaultType) {
448
+ return tree.map((node) => updateNodeType(node, oldType, defaultType));
449
+ }
450
+
451
+ function updateNodeType(node, oldType, defaultType) {
452
+ const type = node.type.id === oldType.id ? defaultType : node.type;
453
+
454
+ return {
455
+ ...node,
456
+ type,
457
+ children: node.children
458
+ ? updateTreeTypes(node.children, oldType, defaultType)
459
+ : [],
460
+ };
461
+ }
462
+
463
+ function flattenAndSort(nodes) {
464
+ const result = [];
465
+
466
+ function traverse(nodeList) {
467
+ for (const node of nodeList) {
468
+ result.push(node);
469
+ if (Array.isArray(node.children) && node.children.length > 0) {
470
+ traverse(node.children);
471
+ }
472
+ }
473
+ }
474
+
475
+ traverse(nodes);
476
+
477
+ // sort by start
478
+ return result.sort((a, b) => a.indices[0] - b.indices[0]);
479
+ }
@@ -1,3 +1,25 @@
1
+ :root
2
+ --text-line-height: 3em
3
+ --main-extra-width: 200px
4
+
5
+ .page-wrapper
6
+ display: flex
7
+ flex-direction: row
8
+ position: relative
9
+ gap: 2em
10
+ align-items: flex-start // makes control-content lose stick
11
+
12
+ .feedback-container
13
+ flex: 4
14
+
15
+ .control-panel
16
+ flex: 1
17
+ height: auto
18
+
19
+ .control-content
20
+ position: sticky
21
+ top: 0
22
+
1
23
  .feedback-component
2
24
  position: relative
3
25
  width: 800px
@@ -8,22 +30,20 @@
8
30
  .node
9
31
  cursor: pointer
10
32
 
33
+ circle
34
+ cursor: pointer
35
+ border: 1px solid black
11
36
 
37
+ .selected
38
+ border: 1px solid white
39
+
12
40
  .feedback-text
13
41
  margin-bottom: 2em
14
42
 
15
43
  .entity-panel
16
44
  position: relative
17
45
  max-height: 600px
18
-
19
- .control-panel
20
- max-width: 15em
21
- position: absolute
22
- top: 1em
23
- right: 1em
24
- padding: 0.2em 0.5em
25
-
26
- .entity-panel
46
+ width: calc(100% - 2em)
27
47
  flex: 1
28
48
  min-height: 100px
29
49
  padding: 1em
@@ -35,3 +55,95 @@
35
55
  .selection-tree
36
56
  margin: -1em 0
37
57
  padding: 1em 0
58
+
59
+ .type-list
60
+ display: grid
61
+ grid-auto-flow: column
62
+ grid-template-rows: repeat(10, auto)
63
+ gap: 0.2em
64
+
65
+ .type-tag
66
+ padding: .2em .5em
67
+ border-radius: .2em
68
+
69
+ .description
70
+ max-width: 300px
71
+ padding: .5em
72
+
73
+ mark
74
+ border-radius: .2em
75
+ cursor: pointer
76
+ color: black !important
77
+
78
+ [role="treeitem"]
79
+ width: auto !important
80
+
81
+ .highlight
82
+ cursor: pointer
83
+ padding: .2em 0
84
+ border-radius: .2em
85
+ position: relative
86
+ zIndex: 10
87
+
88
+ .feedback-text-wrapper
89
+ position: relative
90
+ z-index: 0
91
+ overflow: visible
92
+ line-height: var(--text-line-height)
93
+
94
+ .type-container
95
+ display: flex
96
+ justify-content: space-between
97
+ align-items: center
98
+ column-gap: 1em
99
+
100
+ .add-type
101
+ cursor: pointer
102
+ display: flex
103
+ justify-content: space-between
104
+ padding: 0 .5em
105
+ align-items: center
106
+
107
+ p
108
+ margin: 0
109
+
110
+ .overlay-container
111
+ height: 80vh
112
+ width: 100vw
113
+ display: flex
114
+ justify-content: center
115
+ align-items: center
116
+
117
+ .add-type-overlay
118
+ background-color: var(--secondary-color)
119
+ padding: .5em 1em
120
+ display: flex
121
+ flex-direction: column
122
+ gap: 1em
123
+ border-radius: .2em
124
+
125
+ .title
126
+ display: flex
127
+ justify-content: space-between
128
+ align-items: center
129
+
130
+ h2
131
+ margin: 0
132
+
133
+ .form-group
134
+ display: flex
135
+ gap: 3em
136
+
137
+ .text-inputs
138
+ display: flex
139
+ flex-direction: column
140
+ gap: 1em
141
+
142
+ .icons
143
+ display: flex
144
+ gap: .25em
145
+
146
+ .node-label
147
+ fill: var(--text-emphasized-color)
148
+ fontSize: 10px
149
+ pointerEvents: none