@macrostrat/feedback-components 1.0.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -0
  3. package/dist/edit-state.e8edb13a.js +251 -0
  4. package/dist/edit-state.e8edb13a.js.map +1 -0
  5. package/dist/extractions.54be85f8.js +177 -0
  6. package/dist/extractions.54be85f8.js.map +1 -0
  7. package/dist/feedback.46c2b5c4.js +252 -0
  8. package/dist/feedback.46c2b5c4.js.map +1 -0
  9. package/dist/feedback.module.7e16830e.css +44 -0
  10. package/dist/feedback.module.7e16830e.css.map +1 -0
  11. package/dist/feedback.module.c28cbac7.js +28 -0
  12. package/dist/feedback.module.c28cbac7.js.map +1 -0
  13. package/dist/graph.cb42b871.js +83 -0
  14. package/dist/graph.cb42b871.js.map +1 -0
  15. package/dist/index.d.ts +145 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +9 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/main.module.21bbfaf4.js +19 -0
  20. package/dist/main.module.21bbfaf4.js.map +1 -0
  21. package/dist/main.module.ca3db294.js +16 -0
  22. package/dist/main.module.ca3db294.js.map +1 -0
  23. package/dist/main.module.d6508c0e.css +14 -0
  24. package/dist/main.module.d6508c0e.css.map +1 -0
  25. package/dist/main.module.f9f92ece.css +17 -0
  26. package/dist/main.module.f9f92ece.css.map +1 -0
  27. package/dist/node.30d0b8c3.js +59 -0
  28. package/dist/node.30d0b8c3.js.map +1 -0
  29. package/dist/text-visualizer.77af0d24.js +101 -0
  30. package/dist/text-visualizer.77af0d24.js.map +1 -0
  31. package/dist/type-selector.e75dd247.js +62 -0
  32. package/dist/type-selector.e75dd247.js.map +1 -0
  33. package/package.json +48 -0
  34. package/src/extractions/index.ts +219 -0
  35. package/src/extractions/main.module.sass +10 -0
  36. package/src/extractions/types.ts +30 -0
  37. package/src/feedback/edit-state.ts +311 -0
  38. package/src/feedback/feedback.module.sass +37 -0
  39. package/src/feedback/graph.ts +98 -0
  40. package/src/feedback/index.ts +271 -0
  41. package/src/feedback/node.ts +65 -0
  42. package/src/feedback/text-visualizer.ts +116 -0
  43. package/src/feedback/type-selector/index.ts +75 -0
  44. package/src/feedback/type-selector/main.module.sass +13 -0
  45. package/src/feedback/types.ts +76 -0
  46. package/src/index.ts +2 -0
  47. package/stories/feedback.stories.ts +40 -0
  48. package/stories/test-data.ts +330 -0
@@ -0,0 +1,65 @@
1
+ import { NodeApi, TreeApi } from "react-arborist";
2
+ import { TreeData } from "./types";
3
+ import { EntityTag } from "../extractions";
4
+ import { useTreeDispatch } from "./edit-state";
5
+ import styles from "./feedback.module.sass";
6
+ import hyper from "@macrostrat/hyper";
7
+
8
+ const h = hyper.styled(styles);
9
+
10
+ function isSelected(searchNode: TreeData, treeNode: TreeData) {
11
+ return searchNode.id == treeNode.id;
12
+ // We could also select children of the search node here if we wanted to
13
+ }
14
+
15
+ function isNodeHighlighted(node: NodeApi<TreeData>, tree: TreeApi<TreeData>) {
16
+ // We treat no selection as all nodes being active. We may add some nuance later
17
+ if (tree.selectedNodes.length == 0) {
18
+ return true;
19
+ }
20
+
21
+ for (const selectedNode of tree.selectedNodes) {
22
+ if (isSelected(node.data, selectedNode.data)) {
23
+ return true;
24
+ }
25
+ }
26
+
27
+ // Check if the parent node is highlighted
28
+ if (node.parent != null && isNodeHighlighted(node.parent, tree)) {
29
+ return true;
30
+ }
31
+
32
+ return false;
33
+ }
34
+
35
+ function isNodeActive(node: NodeApi<TreeData>, tree: TreeApi<TreeData>) {
36
+ for (const selectedNode of tree.selectedNodes) {
37
+ if (isSelected(node.data, selectedNode.data)) {
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+
44
+ function Node({ node, style, dragHandle, tree, matchComponent }: any) {
45
+ let highlighted: boolean = isNodeHighlighted(node, tree);
46
+ let active: boolean = isNodeActive(node, tree);
47
+
48
+ const dispatch = useTreeDispatch();
49
+
50
+ return h(
51
+ "div.node",
52
+ { style, ref: dragHandle },
53
+ h(EntityTag, {
54
+ data: node.data,
55
+ active,
56
+ highlighted,
57
+ matchComponent,
58
+ onClickType() {
59
+ dispatch({ type: "toggle-entity-type-selector" });
60
+ },
61
+ })
62
+ );
63
+ }
64
+
65
+ export default Node;
@@ -0,0 +1,116 @@
1
+ import { AnnotateBlendTag, TextAnnotateBlend } from "react-text-annotate-blend";
2
+ import { InternalEntity } from "./types";
3
+ import { TreeDispatch } from "./edit-state";
4
+ import styles from "./feedback.module.sass";
5
+ import hyper from "@macrostrat/hyper";
6
+ import { buildHighlights, getTagStyle } from "../extractions";
7
+ import { Highlight } from "../extractions/types";
8
+ import { useCallback } from "react";
9
+
10
+ const h = hyper.styled(styles);
11
+
12
+ export interface FeedbackTextProps {
13
+ text: string;
14
+ selectedNodes: number[];
15
+ nodes: InternalEntity[];
16
+ updateNodes: (nodes: string[]) => void;
17
+ dispatch: TreeDispatch;
18
+ }
19
+
20
+ function buildTags(
21
+ highlights: Highlight[],
22
+ selectedNodes: number[]
23
+ ): AnnotateBlendTag[] {
24
+ let tags: AnnotateBlendTag[] = [];
25
+
26
+ // If entity ID has already been seen, don't add it again
27
+ const entities = new Set<number>();
28
+
29
+ for (const highlight of highlights) {
30
+ // Don't add multiply-linked entities multiple times
31
+ if (entities.has(highlight.id)) continue;
32
+
33
+ const highlighted = isHighlighted(highlight, selectedNodes);
34
+ const active = isActive(highlight, selectedNodes);
35
+
36
+ tags.push({
37
+ markStyle: {
38
+ ...getTagStyle(highlight.backgroundColor, {
39
+ highlighted,
40
+ active,
41
+ }),
42
+ borderRadius: "0.2em",
43
+ padding: "0.1em",
44
+ borderWidth: "1.5px",
45
+ cursor: "pointer",
46
+ },
47
+ tagStyle: {
48
+ display: "none",
49
+ },
50
+ ...highlight,
51
+ });
52
+
53
+ entities.add(highlight.id);
54
+ }
55
+
56
+ return tags;
57
+ }
58
+
59
+ function isActive(tag: Highlight, selectedNodes: number[]) {
60
+ return selectedNodes.includes(tag.id);
61
+ }
62
+
63
+ function isHighlighted(tag: Highlight, selectedNodes: number[]) {
64
+ if (selectedNodes.length === 0) return true;
65
+ return (
66
+ (selectedNodes.includes(tag.id) ||
67
+ tag.parents?.some((d) => selectedNodes.includes(d))) ??
68
+ false
69
+ );
70
+ }
71
+
72
+ export function FeedbackText(props: FeedbackTextProps) {
73
+ // Convert input to tags
74
+ const { text, selectedNodes, nodes, dispatch } = props;
75
+ let allTags: AnnotateBlendTag[] = buildTags(
76
+ buildHighlights(nodes, null),
77
+ selectedNodes
78
+ );
79
+
80
+ const onChange = useCallback(
81
+ (tags) => {
82
+ // New tags
83
+ console.log(tags);
84
+ const newTags = tags.filter((d) => !("id" in d));
85
+ if (newTags.length > 0) {
86
+ const { start, end } = newTags[0];
87
+ const payload = { start, end, text: text.slice(start, end) };
88
+ dispatch({ type: "create-node", payload });
89
+ return;
90
+ }
91
+
92
+ const tagIDs = new Set(tags.map((d) => d.id));
93
+ const removedIds = allTags.map((d) => d.id).filter((d) => !tagIDs.has(d));
94
+
95
+ /* Find the id that was removed: that is the one that will be selected
96
+ (we are hijacking the 'click to delete' functionality to select instead) */
97
+ if (removedIds.length > 0) {
98
+ dispatch({
99
+ type: "toggle-node-selected",
100
+ payload: { ids: removedIds },
101
+ });
102
+ }
103
+ },
104
+ [allTags, text]
105
+ );
106
+
107
+ return h(TextAnnotateBlend, {
108
+ style: {
109
+ fontSize: "1.2em",
110
+ },
111
+ className: "feedback-text",
112
+ content: text,
113
+ onChange,
114
+ value: allTags,
115
+ });
116
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Entity type selector
3
+ */
4
+
5
+ import styles from "./main.module.sass";
6
+ import hyper from "@macrostrat/hyper";
7
+
8
+ import classNames from "classnames";
9
+ import React from "react";
10
+ import { Omnibar, OmnibarProps } from "@blueprintjs/select";
11
+ import "@blueprintjs/select/lib/css/blueprint-select.css";
12
+
13
+ const h = hyper.styled(styles);
14
+
15
+ interface TagItemProps<T> {
16
+ selected: boolean;
17
+ active: boolean;
18
+ className?: string;
19
+ item: T;
20
+
21
+ onSelect(t: T): void;
22
+
23
+ children?: React.ReactElement;
24
+ }
25
+
26
+ const TagListItem: React.ComponentType<TagItemProps<T>> = (props) => {
27
+ /** Render a tag for the omnibox list */
28
+ let { active, selected, className, onSelect, item, children } = props;
29
+ className = classNames({ active, selected }, className);
30
+ const onClick = () => onSelect(item);
31
+
32
+ return h(
33
+ "div.item-container",
34
+ {
35
+ key: item.id,
36
+ className,
37
+ onClick,
38
+ },
39
+ [
40
+ h("div.swatch", { style: { backgroundColor: item.color } }),
41
+ h("div.item", {}, item.name),
42
+ ]
43
+ );
44
+ };
45
+
46
+ type BoxLifecycleProps<T> = Pick<OmnibarProps<T>, "onClose" | "isOpen">;
47
+
48
+ interface OmniboxProps<T> extends BoxLifecycleProps<T> {
49
+ items: T[];
50
+ selectedItem: T;
51
+ onSelectItem: (t: T) => void;
52
+ listItemComponent?: React.ComponentType<TagItemProps<T>>;
53
+ }
54
+
55
+ export function OmniboxSelector<T>(props: OmniboxProps<T>) {
56
+ /** A general omnibox for annotation types */
57
+ const { onSelectItem, items, isOpen, onClose } = props;
58
+
59
+ return h(Omnibar, {
60
+ onItemSelect: onSelectItem,
61
+ items,
62
+ resetOnSelect: false,
63
+ isOpen,
64
+ onClose,
65
+ itemRenderer(item: T, { handleClick, modifiers }) {
66
+ return h(TagListItem, {
67
+ key: item.id,
68
+ item,
69
+ onSelect: handleClick,
70
+ active: modifiers.active,
71
+ selected: modifiers.active,
72
+ });
73
+ },
74
+ });
75
+ }
@@ -0,0 +1,13 @@
1
+ .item-container
2
+ min-height: 40px
3
+ display: flex
4
+ align-items: center
5
+ gap: 1em
6
+
7
+ &.selected
8
+ background-color: var(--panel-background-color)
9
+
10
+ .swatch
11
+ width: 30px
12
+ height: 30px
13
+ border-radius: 4px
@@ -0,0 +1,76 @@
1
+ import { EntityExt } from "../extractions/types";
2
+
3
+ export interface InternalEntity extends EntityExt {
4
+ term_type: string;
5
+ txt_range: number[][];
6
+ children: InternalEntity[];
7
+ orig_id: number;
8
+ id: string;
9
+ }
10
+
11
+ export interface TextData {
12
+ preprocessor_id: string;
13
+ extraction_pipeline_id: string;
14
+ model_id: string;
15
+ paper_id: string;
16
+ hashed_text: string;
17
+ weaviate_id: string;
18
+ paragraph_text: string;
19
+ }
20
+
21
+ export interface Result {
22
+ text: TextData;
23
+ entities?: InternalEntity[];
24
+ }
25
+
26
+ export type TreeData = EntityExt;
27
+
28
+ export interface ServerRelationship {
29
+ src_name: string;
30
+ dst_name: string;
31
+ relationship_type: string;
32
+ }
33
+
34
+ export interface ServerEntity {
35
+ entity_name: string;
36
+ entity_type: string;
37
+ }
38
+
39
+ export interface ServerResponse {
40
+ text: TextData;
41
+ relationships: ServerRelationship[];
42
+ just_entities: ServerEntity[];
43
+ }
44
+
45
+ export interface RunText {
46
+ preprocessor_id: string;
47
+ paper_id: string;
48
+ hashed_text: string;
49
+ weaviate_id: string;
50
+ paragraph_text: string;
51
+ }
52
+
53
+ export interface RunRelationship {
54
+ src: string;
55
+ dst: string;
56
+ relationship_type: string;
57
+ }
58
+
59
+ export interface RunEntity {
60
+ entity: string;
61
+ entity_type: string;
62
+ }
63
+
64
+ export interface RunSource {
65
+ text: RunText;
66
+ relationships: RunRelationship[];
67
+ just_entities: RunEntity[];
68
+ }
69
+
70
+ export interface RunRecord {
71
+ run_id: string;
72
+ extraction_pipeline_id: string;
73
+ user_name: string;
74
+ model_id: string;
75
+ results: RunSource[];
76
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./extractions";
2
+ export * from "./feedback";
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+
3
+ import { FeedbackComponent } from "../src";
4
+ import { data, entityTypes } from "./test-data";
5
+ import h from "@macrostrat/hyper";
6
+
7
+ function FeedbackInterface({ data, types }) {
8
+ const { entities = [], paragraph_text, model, model_run, source_text } = data;
9
+
10
+ return h(FeedbackComponent, {
11
+ entities,
12
+ text: paragraph_text,
13
+ model,
14
+ entityTypes: createMap(types),
15
+ sourceTextID: source_text,
16
+ runID: model_run,
17
+ });
18
+ }
19
+ // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
20
+ const meta: Meta<any> = {
21
+ title: "Text extractions/Text extraction",
22
+ component: FeedbackInterface,
23
+ };
24
+
25
+ export default meta;
26
+
27
+ export const Primary: StoryObj<{}> = {
28
+ args: {
29
+ data,
30
+ types: entityTypes,
31
+ },
32
+ };
33
+
34
+ function createMap(arr) {
35
+ const out = new Map();
36
+ for (const d of arr) {
37
+ out.set(d.id, d);
38
+ }
39
+ return out;
40
+ }