From 5193d53598b7c23b49a20de9c05fbaeb4833da19 Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Wed, 21 Feb 2024 17:45:56 -0500 Subject: replace button icon with circle instead of bolt --- src/client/util/CurrentUserUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 714e33d25..d61a40ffa 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -281,7 +281,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, -- cgit v1.2.3-70-g09d2 From 73f709846365cb266c8489678d5c6713c0c43158 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Tue, 5 Mar 2024 17:23:33 -0500 Subject: difference between flashcard and comparison node --- src/client/documents/Documents.ts | 12 ++++++- src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/nodes/ComparisonBox.tsx | 38 ++++++++++++---------- src/client/views/nodes/DocumentView.tsx | 3 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 2 -- 5 files changed, 37 insertions(+), 21 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..f334e958a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -265,6 +265,7 @@ export class DocumentOptions { _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false); _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button'); + _layout_isFlashcard?: BOOLt = new BoolInfo('whether comparison node should be displayed as a flashcard'); _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption @@ -728,7 +729,16 @@ export namespace Docs { { data: '', layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' }, + options: { + _layout_isFlashcard: false, + backgroundColor: 'gray', + dropAction: 'move', + waitForDoubleClickToClick: 'always', + layout_reflowHorizontal: true, + layout_reflowVertical: true, + layout_nativeDimEditable: true, + systemIcon: 'BsLayoutSplit', + }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 714e33d25..95e7841bf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -241,7 +241,8 @@ export class CurrentUserUtils { creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, - {key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, + {key: "Flashcard", creator: Docs.Create.ComparisonDocument, opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, + //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 116dc48a6..bdf92038f 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -206,24 +206,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; - return ( -
- {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)} -
- {displayBox(`${this.fieldKey}_1`, 0, 0)} -
+ if (this.Document._layout_isFlashcard) { + return
{displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
; + } else { + return ( +
+ {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)} +
+ {displayBox(`${this.fieldKey}_1`, 0, 0)} +
-
(this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, - }} - onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ - > -
+
(this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, + }} + onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ + > +
+
-
- ); + ); + } } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ed5d31664..7e86125e5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -569,6 +569,9 @@ export class DocumentViewInternal extends DocComponent LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); } appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); + + // if(this.layoutDoc.) + appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0193cdbfc..61d6094b9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -931,8 +931,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined), icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); - // if (usepath === undefined || usepath === 'alternate') usepath = 'alternate: hover' - // if (usepath === 'alternate:hover') usepath = undefined; } !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); -- cgit v1.2.3-70-g09d2 From ed5a7755eba7563c01bc3cc40840d0ebb6cd8826 Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Wed, 6 Mar 2024 19:46:09 -0500 Subject: add diagram document --- package-lock.json | 684 +++++++++++++++++++++++- package.json | 1 + src/.DS_Store | Bin 10244 -> 10244 bytes src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 12 +- src/client/util/CurrentUserUtils.ts | 7 +- src/client/views/nodes/DiagramBox.tsx | 86 +++ src/client/views/nodes/DocumentContentsView.tsx | 2 + 8 files changed, 788 insertions(+), 5 deletions(-) create mode 100644 src/client/views/nodes/DiagramBox.tsx (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index 1f1c994c3..4cc6d8d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,6 +128,7 @@ "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", + "mermaid": "^10.8.0", "mobile-detect": "^1.4.5", "mobx": "^6.12.0", "mobx-react": "^9.1.0", @@ -2224,6 +2225,11 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" + }, "node_modules/@bundled-es-modules/pdfjs-dist": { "version": "3.6.172-alpha.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-3.6.172-alpha.1.tgz", @@ -8784,8 +8790,7 @@ "node_modules/@types/d3-scale-chromatic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", - "dev": true + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" }, "node_modules/@types/d3-selection": { "version": "3.0.10", @@ -14803,6 +14808,14 @@ "node": ">= 0.10" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -15107,6 +15120,29 @@ "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" }, + "node_modules/cytoscape": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.28.1.tgz", + "integrity": "sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==", + "dependencies": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, "node_modules/D": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/D/-/D-1.0.0.tgz", @@ -15387,6 +15423,41 @@ "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -15512,6 +15583,15 @@ "node": ">=12" } }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -15566,6 +15646,11 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -15928,7 +16013,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -16031,6 +16115,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -16116,6 +16205,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.670.tgz", "integrity": "sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A==" }, + "node_modules/elkjs": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.2.tgz", + "integrity": "sha512-2Y/RaA1pdgSHpY0YG4TYuYCD2wh97CRvu22eLG3Kz0pgQ/6KbIFTxsTnDc4MH/6hFlg2L/9qXrDMG0nMjP63iw==" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -19980,6 +20074,11 @@ "he": "bin/he" } }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -22098,6 +22197,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -22114,6 +22218,14 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/kruptein": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", @@ -22204,6 +22316,11 @@ "shell-quote": "^1.8.1" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -23033,6 +23150,513 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "node_modules/mermaid": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz", + "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", + "cytoscape-cose-bilkent": "^4.1.0", + "d3": "^7.4.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "^3.0.5", + "elkjs": "^0.9.0", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/mermaid/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mermaid/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mermaid/node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -24060,6 +24684,14 @@ "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz", "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw==" }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -24405,6 +25037,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -29955,6 +30592,17 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", @@ -31878,6 +32526,14 @@ "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-loader": { "version": "9.5.1", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", @@ -33044,6 +33700,23 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", @@ -33240,6 +33913,11 @@ "node": ">= 8" } }, + "node_modules/web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index b27d50d54..cbe32d9ca 100644 --- a/package.json +++ b/package.json @@ -211,6 +211,7 @@ "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", + "mermaid": "^10.8.0", "mobile-detect": "^1.4.5", "mobx": "^6.12.0", "mobx-react": "^9.1.0", diff --git a/src/.DS_Store b/src/.DS_Store index bb607fbc3..e1d584ba0 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1123bcac9..570d2e9c8 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -12,6 +12,7 @@ export enum DocumentType { REC = 'recording', PDF = 'pdf', INK = 'ink', + DIAGRAM='diagram', SCREENSHOT = 'screenshot', FONTICON = 'fonticonbox', SEARCH = 'search', // search query diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..ade5e718c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -55,6 +55,7 @@ import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { PresBox } from '../views/nodes/trails/PresBox'; import { PresElementBox } from '../views/nodes/trails/PresElementBox'; import { VideoBox } from '../views/nodes/VideoBox'; +import { DiagramBox } from '../views/nodes/DiagramBox'; import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; @@ -658,6 +659,13 @@ export namespace Docs { }, }, ], + [ + DocumentType.DIAGRAM, + { + layout: { view: DiagramBox, dataField: defaultDataKey }, + options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' }, + }, + ], [ DocumentType.BUTTON, { @@ -1025,7 +1033,9 @@ export namespace Docs { export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); } - + export function DiagramDocument(options: DocumentOptions = { title: 'bruh box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.DIAGRAM), undefined, options); + } export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d61a40ffa..8275ec6b8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -248,6 +248,7 @@ export class CurrentUserUtils { {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, @@ -266,7 +267,10 @@ export class CurrentUserUtils { }, funcs: {title: 'this.text?.Text'}}, ]; - emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs)); + emptyThings.forEach( + thing =>{ DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs); + console.log(thing.key) + }); return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, @@ -277,6 +281,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, + { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "circle", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx new file mode 100644 index 000000000..e2af0b670 --- /dev/null +++ b/src/client/views/nodes/DiagramBox.tsx @@ -0,0 +1,86 @@ +import { action, computed, makeObservable, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { Doc, Opt } from '../../../fields/Doc'; +import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { Docs } from '../../documents/Documents'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; +import './ComparisonBox.scss'; +import { FieldView, FieldViewProps } from './FieldView'; +import { PinProps, PresBox } from './trails'; +import mermaid from 'mermaid'; + +interface MermaidProps { + chart: String; + } + +class Mermaid extends React.Component { + componentDidMount() { + mermaid.contentLoaded(); + } + render() { + return
{this.props.chart}
; + } + } + +@observer +export class DiagramBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { + + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DiagramBox, fieldKey); + } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + mermaid.initialize({ startOnLoad: true }); + } + + @observable _animating = ''; + + @computed get clipWidth() { + return NumCast(this.layoutDoc[this.clipWidthKey], 50); + } + get clipWidthKey() { + return '_' + this._props.fieldKey + '_clipWidth'; + } + + componentDidMount() { + this._props.setContentViewBox?.(this); + } + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + const anchor = Docs.Create.ConfigDocument({ + title: 'CompareAnchor:' + this.Document.title, + // set presentation timing properties for restoring view + presentation_transition: 1000, + annotationOn: this.Document, + }); + if (anchor) { + if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; + /* addAsAnnotation &&*/ this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); + return anchor; + } + return this.Document; + }; + docStyleProvider = (doc: Opt, props: Opt, property: string): any => { + if (property === StyleProp.PointerEvents) return 'none'; + return this._props.styleProvider?.(doc, props, property); + }; + _closeRef = React.createRef(); + render() { + return ( +
+ B; + B-->C; + B-->D[hi]; + `}/> +
+ ); + } +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 07e179246..040c8364d 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -23,6 +23,7 @@ import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; import { ComparisonBox } from './ComparisonBox'; +import { DiagramBox } from './DiagramBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; @@ -263,6 +264,7 @@ export class DocumentContentsView extends ObservableReactComponent Date: Sun, 10 Mar 2024 13:10:43 -0400 Subject: test --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 5 +- src/client/views/nodes/DiagramBox.tsx | 88 ++++++++++++++++------------------- 3 files changed, 45 insertions(+), 49 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ade5e718c..a6e229ba7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -252,6 +252,7 @@ export class DocumentOptions { layout_hideLinkButton?: BOOLt = new BoolInfo('whether the blue link counter button should be hidden'); layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected'); _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); + layout_diagramEditor?: STRt = new StrInfo("specify the JSX string for a diagram editor view") layout_borderRounding?: string; _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false); _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8275ec6b8..0279ca2c4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -31,6 +31,7 @@ import { ScriptingGlobals } from "./ScriptingGlobals"; import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; +import { CollectionView } from "../views/collections/CollectionView"; interface Button { // DocumentOptions fields a button can set @@ -248,7 +249,7 @@ export class CurrentUserUtils { {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, - {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300 }}, + {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300, _type_collection: CollectionViewType.Freeform, layout_diagramEditor: CollectionView.LayoutString("data") }, scripts: { onPaint: `toggleDetail(documentView, "diagramEditor","")`}}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, @@ -281,7 +282,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, - { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "circle", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, + { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index e2af0b670..376dff15d 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -1,85 +1,79 @@ -import { action, computed, makeObservable, observable } from 'mobx'; +import { makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { Docs } from '../../documents/Documents'; -import { DragManager } from '../../util/DragManager'; -import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import mermaid from 'mermaid'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; interface MermaidProps { - chart: String; - } + chart: String; +} class Mermaid extends React.Component { componentDidMount() { - mermaid.contentLoaded(); + mermaid.contentLoaded(); } render() { - return
{this.props.chart}
; + return
{this.props.chart}
; } - } +} @observer export class DiagramBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { - + @observable chartContent: string = ''; + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DiagramBox, fieldKey); } constructor(props: FieldViewProps) { super(props); makeObservable(this); - mermaid.initialize({ startOnLoad: true }); + //this.createMermaidCode(); + this.chartContent = 'graph LR;A-->B;B-->C; B-->D[hello];'; } - @observable _animating = ''; - - @computed get clipWidth() { - return NumCast(this.layoutDoc[this.clipWidthKey], 50); - } - get clipWidthKey() { - return '_' + this._props.fieldKey + '_clipWidth'; - } - componentDidMount() { this._props.setContentViewBox?.(this); + mermaid.initialize({ startOnLoad: true }); } - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - const anchor = Docs.Create.ConfigDocument({ - title: 'CompareAnchor:' + this.Document.title, - // set presentation timing properties for restoring view - presentation_transition: 1000, - annotationOn: this.Document, - }); - if (anchor) { - if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; - /* addAsAnnotation &&*/ this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); - return anchor; + createMermaidCode = (): void => { + if (this.Document.data instanceof List) { + let docArray: Doc[] = DocListCast(this.Document.data); + let mermaidCode = 'graph LR;'; + docArray.map(doc => { + if (doc.title == 'rectangle') { + DocListCast(this.Document.data).map(lineDoc => { + if ((lineDoc.title == 'stroke' || lineDoc.title == 'line') && typeof lineDoc.x === 'number' && typeof doc.x === 'number' && typeof doc.width === 'number'&& typeof doc.height === 'number'&& typeof doc.y === 'number'&& typeof lineDoc.y === 'number') { + if (lineDoc.x < doc.x + doc.width + (doc.width + doc.x) * 0.1 && lineDoc.x > doc.x&&lineDoc.y>doc.y&&lineDoc.y { + if (doc2.title == 'rectangle' && typeof lineDoc.x === 'number' && typeof lineDoc.width === 'number' && typeof doc2.x === 'number' && typeof doc2.width === 'number'&& typeof doc2.y === 'number'&& typeof doc2.height === 'number'&& typeof lineDoc.y === 'number') { + if (lineDoc.x + lineDoc.width > doc2.x - (doc2.x - doc2.width) * 0.1 && lineDoc.x + lineDoc.width < doc2.x + doc2.width &&lineDoc.y>doc2.y&&lineDoc.y' + doc2.title + Math.floor(doc2.x).toString() + ';'; + const indexToRemove = docArray.findIndex(doc => doc === lineDoc); + if (indexToRemove !== -1) { + docArray.splice(indexToRemove, 1); + } + } + } + }); + } + } + }); + } + this.chartContent = mermaidCode; + }); } - return this.Document; - }; - docStyleProvider = (doc: Opt, props: Opt, property: string): any => { - if (property === StyleProp.PointerEvents) return 'none'; - return this._props.styleProvider?.(doc, props, property); }; - _closeRef = React.createRef(); render() { + console.log(this.chartContent) return (
- B; - B-->C; - B-->D[hi]; - `}/> +
); } -- cgit v1.2.3-70-g09d2 From 2ca4e70cd030fb9199d149060adf1b1d7c07857c Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Tue, 12 Mar 2024 16:40:40 -0400 Subject: fix --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 2 ++ src/client/views/nodes/DiagramBox.scss | 3 +++ src/client/views/nodes/DiagramBox.tsx | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/client/views/nodes/DiagramBox.scss (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2f177b62a..96f4e4597 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -276,6 +276,7 @@ export class DocumentOptions { layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected'); _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); layout_diagramEditor?: STRt = new StrInfo("specify the JSX string for a diagram editor view") + layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); layout_borderRounding?: string; _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false); _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a41fd8796..18b37ffd1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -33,6 +33,8 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { CollectionView } from "../views/collections/CollectionView"; +import { LabelBox } from "../views/nodes/LabelBox"; +import { ImageBox } from "../views/nodes/ImageBox"; interface Button { // DocumentOptions fields a button can set diff --git a/src/client/views/nodes/DiagramBox.scss b/src/client/views/nodes/DiagramBox.scss new file mode 100644 index 000000000..50d9a6573 --- /dev/null +++ b/src/client/views/nodes/DiagramBox.scss @@ -0,0 +1,3 @@ +.mermaid{ + width:100%; +} \ No newline at end of file diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 9ff2d27d2..e162360f8 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; -import './ComparisonBox.scss'; +import './DiagramBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import mermaid from 'mermaid'; -- cgit v1.2.3-70-g09d2 From e95d25eb8159bb7c753fa27e74e9baa8d3bffea6 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Sat, 30 Mar 2024 00:06:27 -0400 Subject: working on making flashcards from pdf with ai --- src/.DS_Store | Bin 10244 -> 10244 bytes src/client/apis/gpt/GPT.ts | 8 +- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 5 +- .../views/collections/CollectionCarouselView.scss | 18 +- .../views/collections/CollectionCarouselView.tsx | 76 +++++++++ src/client/views/collections/CollectionView.tsx | 9 + src/client/views/nodes/ComparisonBox.tsx | 181 ++++++++++++++++----- src/client/views/pdf/AnchorMenu.tsx | 23 +++ src/client/views/pdf/GPTPopup/GPTPopup.tsx | 39 ++++- src/client/views/pdf/PDFViewer.tsx | 4 + 11 files changed, 315 insertions(+), 52 deletions(-) (limited to 'src/client/util') diff --git a/src/.DS_Store b/src/.DS_Store index f8d745dbf..f1f08fbb5 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cea862330..968b45273 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -4,6 +4,7 @@ enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + FLASHCARD = 'flashcard', } type GPTCallOpts = { @@ -14,9 +15,10 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Make flashcards out of this text with questions on the front and answers on the back: ' }, + completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, }; /** diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2d13519..bf3d14560 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1054,8 +1054,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), '', options); } - export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { - return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); + export function ComparisonDocument(text: string, options: DocumentOptions = { title: 'Comparison Box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), text, options); } export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 958240f94..ab0315ba1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -341,14 +341,15 @@ pie title Minerals in my tap water creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, - {key: "Flashcard", creator: Docs.Create.ComparisonDocument, opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, + {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, + // {key: "Flashcard", creator: Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, - {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Comparison", creator: opts => Docs.Create.ComparisonDocument("",opts), opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 130b31325..f115bb40a 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -13,7 +13,10 @@ } } .carouselView-back, -.carouselView-fwd { +.carouselView-fwd, +.carouselView-star, +.carouselView-remove, +.carouselView-check { position: absolute; display: flex; top: 42.5%; @@ -34,6 +37,19 @@ .carouselView-back { left: 20; } +.carouselView-star { + top: 0; + right: 20; +} +.carouselView-remove { + top: 80%; + left: 52%; +} +.carouselView-check { + top: 80%; + right: 52%; +} + .carouselView-back:hover, .carouselView-fwd:hover { background: lightgray; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index dae16bafb..7f5176123 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -36,11 +36,67 @@ export class CollectionCarouselView extends CollectionSubView() { advance = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; + var startInd = this.layoutDoc._carousel_index; + + // if the star filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + // go to a new index that is starred, skip the ones that aren't + while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd + 1) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + + // if the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + // go to a new index that is missed, skip the ones that are correct + while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd + 1) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } }; goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + + var startInd = this.layoutDoc._carousel_index; + + // if the star filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + // go to a new index that is starred, skip the ones that aren't + while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + + // if the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + // go to a new index that is missed, skip the ones that are correct + while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + }; + + star = (e: React.MouseEvent) => { + e.stopPropagation(); + // stars the document when the button is pressed + const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; + else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; + }; + + missed = (e: React.MouseEvent, val: string) => { + e.stopPropagation(); + const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + curDoc.layout[`${this.fieldKey}_missed`] = val; + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; + this.advance; }; + captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string): any => { // first look for properties on the document in the carousel, then fallback to properties on the container const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; @@ -106,6 +162,15 @@ export class CollectionCarouselView extends CollectionSubView() {
+
+ +
+
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + +
+
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + +
); } @@ -120,6 +185,17 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} +

+ Recently missed! +

{this.Document._chromeHidden ? null : this.buttons}
); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 18eb4dd1f..168176edf 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -176,6 +176,15 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' }); + revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' }); + revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' }); + + //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); + !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 81f624b20..ce862cd22 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,13 +4,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; +import { Tooltip } from '@mui/material'; import { CSSTransition } from 'react-transition-group'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; @@ -18,6 +19,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextField } from '../../../fields/RichTextField'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import { DocData } from '../../../fields/DocSymbols'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -88,7 +91,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() if (this._isAnyChildContentActive) return; this._animating = 'all 200ms'; // on click, animate slider movement to the targetWidth - this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + // this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight(); setTimeout( action(() => (this._animating = '')), @@ -128,7 +132,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() clearDoc = (fieldKey: string) => { delete this.dataDoc[fieldKey]; this.dataDoc[fieldKey] = 'empty'; - // this.dataDoc[this.fieldKey + '_1'] = 'empty'; }; // clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey]; @@ -182,47 +185,72 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; + hoverFlip = (side: string | undefined) => { + if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; + }; @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( -
- setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { - console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); - if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { - this.flipFlashcard(); - } - }) - } - style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', - background: usepath === 'alternate' ? 'white' : 'black', - color: usepath === 'alternate' ? 'black' : 'white', - }}> - -
+ flip
}> +
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { + console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); + if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { + this.flipFlashcard(); + } + }) + } + style={{ + //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', + background: usepath === 'alternate' ? 'white' : 'black', + color: usepath === 'alternate' ? 'black' : 'white', + }}> + +
+ ); } render() { const clearButton = (which: string) => { return ( -
this.closeDown(e, which)} // prevent triggering slider movement in registerSliding - > - -
+ remove
}> +
this.closeDown(e, which)} // prevent triggering slider movement in registerSliding + > + +
+ ); }; const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot - const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + //const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text; + const layoutTemplateString = !targetDoc + ? which.endsWith('0') && subjectText !== undefined + ? FormattedTextBox.LayoutString(this.fieldKey) + : which.endsWith('1') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string') + ? FormattedTextBox.LayoutString(which) + : undefined + : undefined; + + if (which.endsWith('1') && !layoutTemplateString && targetDoc) { + const queryText = RTFCast(this.Document[this.fieldKey + '_1'])?.Text; + console.log('QUER' + queryText); + if (queryText?.includes('--TEXT--') && subjectText) { + this.Document[DocData][this.fieldKey + '_1'] = ''; + gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_1'] = value.trim())); + } + } + return targetDoc || layoutTemplateString ? ( <> () moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={() => NumCast(this.layoutDoc.width, 200)} - NativeHeight={() => NumCast(this.layoutDoc.height, 200)} + NativeHeight={(): number => { + return NumCast(this.layoutDoc.height, 200); + }} isContentActive={emptyFunction} isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} @@ -251,6 +281,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; const displayBox = (which: string, index: number, cover: number) => { + // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100; + // else this.layoutDoc.height = 300; return (
this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} @@ -258,26 +290,89 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; + const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => { + return ( +
+
this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which, 0)}> + {displayDoc(which)} +
+
this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which2, 1)}> + {displayDoc(which2)} +
+
+ ); + }; + if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + // add text box when first created + if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { + console.log('TEXT HERE' + this.dataDoc.data); + const newDoc = Docs.Create.TextDocument(StrCast(this.dataDoc.data)); + this.addDoc(newDoc, this.fieldKey + '_0'); + } + // if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); - return ( -
{ - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; - }} - onMouseLeave={() => { - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; - }}> - {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') { + { + return ( +
+ {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)} + {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)} +
+ ); + // return ( + //
+ // {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelHeight() - 3)} + //
+ // {displayBox(`${this.fieldKey}_1`, 0, 0)} + //
+ //
+ // ); + // return ( + //
+ // {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelHeight() / 2)} + // {displayBox(`${this.fieldKey}_${side === 0 ? 0 : 1}`, 1, this._props.PanelHeight() / 2)} - {this.overlayAlternateIcon} -
- ); + //
+ // ); + } + } else { + return ( +
{ + this.hoverFlip('alternate'); + }} + onMouseLeave={() => { + this.hoverFlip(undefined); + }}> + {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} + {/* {displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + {displayBoxReveal(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} */} + {/*
{displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}
*/} + {/*
+ {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} +
*/} + + {this.overlayAlternateIcon} +
+ ); + } } else { return (
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d0688c338..844c1e36d 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { ColorResult } from 'react-color'; import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocUtils, Docs } from '../../documents/Documents'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { SelectionManager } from '../../util/SelectionManager'; @@ -87,6 +88,22 @@ export class AnchorMenu extends AntimodeMenu { GPTPopup.Instance.setLoading(false); }; + gptFlashcards = async (e: React.PointerEvent) => { + // move this logic to gptpopup, need to implement generate again + // GPTPopup.Instance.setVisible(true); + // GPTPopup.Instance.setMode(GPTPopupMode.FLASHCARD); + // GPTPopup.Instance.setLoading(true); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD); + GPTPopup.Instance.setText(res || 'Something went wrong.'); + GPTPopup.Instance.transferToFlashcard(); + } catch (err) { + console.error(err); + } + GPTPopup.Instance.setLoading(false); + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -176,6 +193,12 @@ export class AnchorMenu extends AntimodeMenu { color={SettingsManager.userColor} /> )} + } + color={SettingsManager.userColor} + /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( { } }; + transferToFlashcard = () => { + const senArr = this.text.split('.'); + for (var i = 0; i < senArr.length; i += 2) { + console.log('SEN: ' + senArr[i]); + const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); + newDoc.text = senArr[i]; + this.addDoc(newDoc, this.sidebarId); + const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); + if (anchor) { + DocUtils.MakeLink(newDoc, anchor, { + link_relationship: 'GPT Summary', + }); + } + } + }; + /** * Transfers the image urls to actual image docs */ @@ -213,6 +232,23 @@ export class GPTPopup extends ObservableReactComponent { ); }; + flashcardBox = () => { + // const textArr = this.text.split("."); + // textArr.forEach(function(sentence) { + // console.log(sentence); + + // }); + // const newDoc = Docs.Create.ComparisonDocument(); + // this.addToCollection?.(newDoc); + // // const newDoc = Docs.Create.ComparisonDocument(); + // DocUtils.copyDragFactory(Doc.UserDoc().emptyFlashcard as Doc); + // // this.addToCollection?.(newDoc); + // // return newDoc; + // + const newDoc = Docs.Create.TextDocument('Hello there'); + this.addDoc?.(newDoc); + }; + data = () => { return (
@@ -268,6 +304,7 @@ export class GPTPopup extends ObservableReactComponent { <> } color={StrCast(Doc.UserDoc().userVariantColor)} /> ); + } + + const totalWidth = amButtons * 35 + (amButtons * 2* 5) + 6; + return ( +
+ {buttons} +
+ ); + } + + + + + + + + + + + // @computed get content() { @@ -450,7 +533,7 @@ export class CollectionCardView extends CollectionSubView() { let translate = 0 - if (this.inactiveDocs().length != this.myChildLayoutPairs.length) { + if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) { translate += this.panelWidth() / 2; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2d05b5490..11193f496 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2010,4 +2010,4 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) { SchemaCSVPopUp.Instance.setVisible(true); } }); -}); +}); \ No newline at end of file diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 526315802..9056917fe 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -105,10 +105,12 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', checkResult?: boolean, persist?: boolean) { + + +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', checkResult?: boolean, persist?: boolean, customNumber?: number) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -156,6 +158,21 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links", }], + ['custom', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom", + }], + + //in an ideal world lol + + + // ['create', { + // checkResult: (doc:Doc) => NumCast(doc?.customSortCount), + // setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1, + // }], + + + ]); if (checkResult) { -- cgit v1.2.3-70-g09d2 From 3c5b2353cb20843e968e51fdff58cc92f101ed51 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 14 May 2024 02:14:58 -0400 Subject: getting close to done --- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 6 ++- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionCardDeckView.scss | 2 +- .../views/collections/CollectionCardDeckView.tsx | 63 +++++++++++++++++----- .../CollectionFreeFormLayoutEngines.tsx | 26 ++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/global/globalScripts.ts | 35 +++++++----- 8 files changed, 94 insertions(+), 49 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df69e215a..57f91399a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -500,7 +500,8 @@ export class DocumentOptions { userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); - customSortCount?: NUMt = new NumInfo('number of custom sorts the user has created'); + customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created'); + customHashMap?: List // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time'); // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type'); // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color'); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 59560dd78..abc728cd3 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -730,7 +730,11 @@ pie title Minerals in my tap water { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Links", icon:"link", toolTip:"Sort by its links", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"links", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"plus", toolTip:"Create new custom groupings!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"robot", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"star", toolTip:"Create your second custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"satellite", toolTip:"Create your third custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + + // ...customs ] diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a5882dcf3..f1296e46a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -527,7 +527,9 @@ export class MainView extends ObservableReactComponent<{}> { fa.faArrowsDownToLine, fa.faPalette, fa.faHourglassHalf, - fa.faRobot + fa.faRobot, + fa.faSatellite, + fa.faStar ] ); } diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 09d6f70ea..babc604b5 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -68,7 +68,7 @@ button { .card-item-active, .card-item { position: relative; - transition: transform 0.3s ease-in-out; + transition: transform 0.5s ease-in-out; } .card-item-active { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 996c5711b..fc183b72b 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -2,7 +2,7 @@ import { ObservableMap, action, computed, makeObservable, observable } from 'mob import { observer } from 'mobx-react'; import * as React from 'react'; import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils'; -import { Doc, DocListCast, Field } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, ScriptCast, StrCast, BoolCast, DocCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; @@ -17,6 +17,7 @@ import { LinkManager } from '../../util/LinkManager'; import { DocumentType } from '../../documents/DocumentTypes'; import { forEach } from 'lodash'; import { SnappingManager } from '../../util/SnappingManager'; +import { List } from '../../../fields/List'; @observer export class CollectionCardView extends CollectionSubView() { @@ -70,11 +71,49 @@ export class CollectionCardView extends CollectionSubView() { super(props); makeObservable(this); // this.rotationDegree(7); + + if (this._props.Document.customHashMap != undefined){ + this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap)) + } + } + + @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; + + @computed get mapToField(): List { + const resultList = new List(); // Creating a new ListImpl instance for strings + + this.customGroupDictionary.forEach(map => { + // Convert each map to a single string with entries formatted as "key:value" + const mapString = Array.from(map.entries()) + .map(([key, value]) => `${key}:${value}`) + .join(','); // Join all key-value pairs with commas + resultList.push(mapString); // Add the formatted string of the current map to the list + }); + + return resultList; + } + + getCustoms = (stringFrom: string[]): Map[] => { + return stringFrom.map(s => { + // Create a new Map object for each string. + const map = new Map(); + // Split the string by commas to get key-value pairs, then process each pair. + s.split(',').forEach(pair => { + const [key, value] = pair.split(':'); + map.set(Number(key), Number(value)); + }); + return map; + }); } + + + + private _dropDisposer?: DragManager.DragDropDisposer; componentWillUnmount() { + console.log("hey hey hey") this._dropDisposer?.(); } @@ -253,9 +292,11 @@ export class CollectionCardView extends CollectionSubView() { break; case 'custom': - typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? ''; + + console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)]) + typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999'; // console.log(typeA + "A") - typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? ''; + typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999'; // console.log(typeB + 'b') break; @@ -397,7 +438,7 @@ export class CollectionCardView extends CollectionSubView() { SnappingManager.SetIsResizing(undefined); this._forceChildXf = !this._forceChildXf; }), - 400 + 700 ); }} style={{ @@ -419,11 +460,13 @@ export class CollectionCardView extends CollectionSubView() { }); } - @observable amGroups = 0; - @observable customGroupDictionary = new Map(); + + + @action toggleButton(childPairIndex: number, buttonID: number) { - this.customGroupDictionary.set(childPairIndex, buttonID); + this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); + this._props.Document.customHashMap = this.mapToField } renderButtons(childPairIndex: number) { @@ -431,11 +474,7 @@ export class CollectionCardView extends CollectionSubView() { let amButtons = 4; - if (this.amGroups > 4) { - amButtons = this.amGroups; - } - - let activeButtonIndex = this.customGroupDictionary.get(childPairIndex); + let activeButtonIndex = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(childPairIndex); for (let i = 0; i < amButtons; i++) { const isActive = activeButtonIndex == i; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index becad63f6..22005eb23 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -126,27 +126,20 @@ export function computeStarburstLayout(poolData: Map, pivotDoc } // export function computeCardDeckLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { -// console.log('hi'); // const docMap = new Map(); -// const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread -// const startX = -(spreadWidth / 2); // Starting X position -// const fanAngle = 5; // Angle in degrees for fanning out cards -// const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order +// const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)]; +// const burstScale = NumCast(pivotDoc._starburstDocScale, 1); // childPairs.forEach(({ layout, data }, i) => { // const aspect = NumCast(layout._height) / NumCast(layout._width); -// const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1); -// const posX = startX + (spreadWidth / childPairs.length) * i; -// const posY = 0; // Adjust if you want to change the vertical alignment -// const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect - +// const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale; +// const deg = (i / childPairs.length) * Math.PI * 2; // docMap.set(layout[Id], { -// x: posX, -// y: posY, +// x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)), +// y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)), // width: docSize, // height: docSize * aspect, -// zIndex: baseZIndex + i, -// rotation: rotation, +// zIndex: NumCast(layout.zIndex), // pair: { layout, data }, // replica: '', // color: 'white', @@ -154,9 +147,8 @@ export function computeStarburstLayout(poolData: Map, pivotDoc // transition: 'all 0.3s', // }); // }); - -// const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined }; -// return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); +// const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined }; +// return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); // } export function computePivotLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 98684ae98..7e7a5c2c6 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -533,7 +533,7 @@ export class MarqueeView extends ObservableReactComponent any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -158,21 +158,28 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links", }], - ['custom', { + ['custom1', { checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom", - }], - - //in an ideal world lol - - - // ['create', { - // checkResult: (doc:Doc) => NumCast(doc?.customSortCount), - // setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1, - // }], + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 0; + } + }], - + ['custom2', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 1; + console.log(doc.customSortNumber + " numberrrrrrrr") + } }], + ['custom3', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 2; + } }], ]); if (checkResult) { -- cgit v1.2.3-70-g09d2 From df7532553db3df270fcbd25a46e7a349a82bd712 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Thu, 16 May 2024 12:52:14 -0400 Subject: pdfs being weird --- src/client/apis/gpt/GPT.ts | 35 +++++------ src/client/util/CurrentUserUtils.ts | 4 +- .../views/collections/CollectionCardDeckView.tsx | 70 ++++++++++++++++++++-- src/client/views/global/globalScripts.ts | 8 ++- src/client/views/nodes/PDFBox.tsx | 1 + 5 files changed, 92 insertions(+), 26 deletions(-) (limited to 'src/client/util') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 30194f9f8..1f50ad0b8 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -5,7 +5,8 @@ enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', - DATA = 'data', + // MERMAID='mermaid' + SORT = 'sort' } type GPTCallOpts = { @@ -15,45 +16,40 @@ type GPTCallOpts = { prompt: string; }; -/** - * Replace completions (deprecated) with chat - */ - const callTypeMap: { [type: string]: GPTCallOpts } = { - // newest model: gpt-4 - summary: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' }, - edit: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, - completion: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, - data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." }, + summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, + sort:{model:'gpt-4-turbo',maxTokens:2048,temp:0,prompt:"I'm going to give you a list of strings. Sort them into lists by shared content. The format of should be: '[string1, string2], [string 3, string4][string5, string6]' where the actual numbers of strings in each category / total categorys is arbritray and up to your interpretation"} }; -/** + +/**` * Calls the OpenAI API. * * @param inputText Text to process * @returns AI Output */ -const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => { +const gptAPICall = async (inputText: string, callType: GPTCallType) => { if (callType === GPTCallType.SUMMARY) inputText += '.'; const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration: ClientOptions = { - apiKey: process.env.OPENAI_KEY, + apiKey: "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", dangerouslyAllowBrowser: true, }; const openai = new OpenAI(configuration); - let usePrompt = prompt ? opts.prompt + prompt : opts.prompt; let messages: ChatCompletionMessageParam[] = [ - { role: 'system', content: usePrompt }, + { role: 'system', content: opts.prompt }, { role: 'user', content: inputText }, ]; const response = await openai.chat.completions.create({ model: opts.model, - max_tokens: opts.maxTokens, + messages: messages, temperature: opts.temp, - messages, + max_tokens: opts.maxTokens, }); const content = response.choices[0].message.content; return content; @@ -76,11 +72,12 @@ const gptImageCall = async (prompt: string, n?: number) => { n: n ?? 1, size: '1024x1024', }); - return response.data.map(data => data.url); + return response.data.map((data: any) => data.url); + // return response.data.data[0].url; } catch (err) { console.error(err); return; } }; -export { gptAPICall, gptImageCall, GPTCallType }; +export { gptAPICall, gptImageCall, GPTCallType }; \ No newline at end of file diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index abc728cd3..e18c22bac 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -730,9 +730,11 @@ pie title Minerals in my tap water { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Links", icon:"link", toolTip:"Sort by its links", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"links", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"robot", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"heart", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Create", icon:"star", toolTip:"Create your second custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Create", icon:"satellite", toolTip:"Create your third custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // ...customs diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 7af6eb614..561b9f806 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils'; import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast } from '../../../fields/Types'; +import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast, Cast} from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { StyleProp } from '../StyleProvider'; @@ -18,6 +18,9 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { forEach } from 'lodash'; import { SnappingManager } from '../../util/SnappingManager'; import { List } from '../../../fields/List'; +import { gptAPICall } from '../../apis/gpt/GPT'; +import { GPTCallType } from '../../apis/gpt/GPT'; +import { ImageField } from '../../../fields/URLField'; @observer export class CollectionCardView extends CollectionSubView() { @@ -249,6 +252,11 @@ export class CollectionCardView extends CollectionSubView() { // console.log('hiiiii'); return this.sort(sorted, 'custom', desc); + case 'chat': + this.smartSort() + docs = this.myChildLayoutPairs; + return {docs} + default: docs = this.myChildLayoutPairs; return { docs }; @@ -306,7 +314,7 @@ export class CollectionCardView extends CollectionSubView() { // console.log(typeA + "A") typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999'; // console.log(typeB + 'b') - break; + break; default: typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string @@ -468,6 +476,8 @@ export class CollectionCardView extends CollectionSubView() { }); } + + @@ -476,10 +486,62 @@ export class CollectionCardView extends CollectionSubView() { this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); this._props.Document.customHashMap = this.mapToField - // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1'])); - // console.log(queryText) + // Cast(selected.Document.data, WebField, null)?.url?.href) + + // StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href) + + + + // const + const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url; + + console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); + + + // const queryText = (RTFCast(this.myChildLayoutPairs[0].layout.text)).Text; + // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text; + + console.log(imgurlperchance ?? "sad") } + childPairStringList () { + let string = "" + for (let i=0; i < this.myChildLayoutPairs.length; i++){ + string += StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text) + ", " + } + return string + } + + @observable isLoading = false + + @action async smartSort() { + this.isLoading = true; + console.log("loading") + let prompt= "" + + if (this.childPairStringList() == ""){ + console.log("no child pairs :(") + } + + else{ + console.log(this.childPairStringList() + "og list") + prompt = "Sort this list of strings by shared content " + this.childPairStringList() + } + + + let res = await gptAPICall(prompt, GPTCallType.SORT); + this.isLoading = false; + if (res == 'Error connecting with API.') { + // If GPT call failed + console.error('GPT call failed'); + } else if (res != null) { + // If GPT call succeeded, set htmlCode;;; TODO: check if valid html + console.log(res) + } + this.isLoading = false; + }; + renderButtons(childPairIndex: number) { const buttons = []; // Array to hold the button elements diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 30602ea0b..14f83beb6 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -107,10 +107,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', checkResult?: boolean, persist?: boolean, customNumber?: number) { +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', checkResult?: boolean, persist?: boolean, customNumber?: number) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -180,6 +180,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid doc.cardSort = "custom"; doc.customSortNumber = 2; } }], + ['chat', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "chat", + }], ]); if (checkResult) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 1274220b6..90d6133e4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -33,6 +33,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; import { PinProps, PresBox } from './trails'; +import { Networking } from '../../Network'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { -- cgit v1.2.3-70-g09d2 From 69e286b504c2f1bbef7d489dc675b9e54ef8d983 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 May 2024 19:52:13 -0400 Subject: refactor/cleanup of pres animations. changed effects to start halfway through a zoom transition. --- src/client/apis/gpt/customization.ts | 18 +- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 6 +- src/client/views/PinFuncs.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/animationtimeline/Timeline.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 18 +- src/client/views/nodes/trails/PresBox.tsx | 50 +-- src/client/views/nodes/trails/PresEnums.ts | 2 +- src/client/views/nodes/trails/SlideEffect.scss | 2 +- src/client/views/nodes/trails/SlideEffect.tsx | 402 +++++------------------- src/client/views/nodes/trails/SpringUtils.ts | 2 +- 12 files changed, 134 insertions(+), 373 deletions(-) (limited to 'src/client/util') diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts index 61b666bd3..2262886a2 100644 --- a/src/client/apis/gpt/customization.ts +++ b/src/client/apis/gpt/customization.ts @@ -25,13 +25,13 @@ export const addCustomizationProperty = (type: CustomizationType, name: string, // includes most fields but is not yet fully comprehensive export const gptSlideProperties = [ 'title', + 'config_zoom', 'presentation_transition', - 'presEaseFunc', + 'presentation_easeFunc', 'presentation_effect', 'presentation_effectDirection', - 'presEffectTiming', - 'config_zoom', - 'presPlayAudio', + 'presentation_effectTiming', + 'presentation_playAudio', 'presentation_zoomText', 'presentation_hideBefore', 'presentation_hide', @@ -43,20 +43,20 @@ export const gptSlideProperties = [ const setupPresSlideCustomization = () => { addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.'); - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presEaseFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_easeFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']); - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Zoom', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effectDirection', 'is what direction the effect is applied.', ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']); addCustomizationProperty( CustomizationType.PRES_TRAIL_SLIDE, - 'presEffectTiming', + 'presentation_effectTiming', "is a json object of the format: {type: string, stiffness: number, damping: number, mass: number}. Type is always “custom”. Controls the spring-based timing of the presentation effect animation. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect." ); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.'); // boolean values - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presPlayAudio', 'is a boolean value indicating if we should play audio when we go to the slide.'); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_playAudio', 'is a boolean value indicating if we should play audio when we go to the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_zoomText', 'is a boolean value indicating if we should zoom into text selections when we go to the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideBefore', 'is a boolean value indicating if we should hide the slide before going to it.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hide', 'is a boolean value indicating if we should hide the slide during the presentation.'); @@ -82,7 +82,7 @@ export const getSlideTransitionSuggestions = async (inputText: string) => { */ const prompt = - "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements. Effect is the type of animation; its only possible values are ['Zoom', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect."; + "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements. Effect is the type of animation; its only possible values are ['Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect."; const customInput = inputText ?? 'Make them as contrasting as possible with different effects and timings ranging from gentle to energetic.'; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1c18c0d84..24dd5cf37 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -343,6 +343,7 @@ export class DocumentOptions { config_map?: STRt = new StrInfo('text location of map', false); config_panX?: NUMt = new NumInfo('panX saved as a view spec', false); config_panY?: NUMt = new NumInfo('panY saved as a view spec', false); + config_zoom?: NUMt = new NumInfo('zoom saved as a view spec', false); config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec', false); presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document', false); presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 09a8194ca..8ad6ddf47 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -338,11 +338,13 @@ export class DocumentManager { // viewSpec !== docView.Document && docView.ComponentView?.focus?.(viewSpec, options); PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); - Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect); + // if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of + // the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen + // bcz: should this delay be an options parameter? + setTimeout(() => Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect), (options.zoomTime ?? 0) * 0.5); if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode)); if (options.playAudio) DocumentManager.playAudioAnno(docView.Document); if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden; - if (options.effect) docView.Document[Animation] = options.effect; if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) { // if the docView is a text anchor, the contextView is the PDF/Web/Text doc diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index 3d998ecaf..430455644 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -54,7 +54,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { pinDoc.config_width = NumCast(targetDoc.width); pinDoc.config_height = NumCast(targetDoc.height); } - if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true; + if (pinProps.pinAudioPlay) pinDoc.presentation_playAudio = true; if (pinProps.pinData) { pinDoc.config_pinData = pinProps.pinData.scrollable || diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index df4ed98ac..024db82a4 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1487,7 +1487,7 @@ export class PropertiesView extends ObservableReactComponent this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> - {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( + {[PresEffect.None, PresEffect.Expand, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index c4d35330b..d9ff21035 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -547,7 +547,7 @@ export class Timeline extends ObservableReactComponent {
{this.drawTicks()}
-
+
{[...this.children, this._props.Document].map(doc => ( diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6f9e14c1b..c59cd0ee4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -934,7 +934,7 @@ export class DocumentViewInternal extends DocComponent - {this._componentView?.isUnstyledView?.() ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} + {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} {borderPath?.jsx}
); @@ -958,7 +958,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc} - // case PresEffect.Fade: return {renderDoc} + case PresEffect.Expand: return {renderDoc} + case PresEffect.Flip: return {renderDoc} + case PresEffect.Rotate: return {renderDoc} + case PresEffect.Bounce: return {renderDoc} + case PresEffect.Roll: return {renderDoc} + // case PresEffect.Fade: return {renderDoc} case PresEffect.Fade: return {renderDoc} - case PresEffect.Flip: return {renderDoc} - case PresEffect.Rotate: return {renderDoc} - case PresEffect.Bounce: return {renderDoc} - case PresEffect.Roll: return {renderDoc} // keep as preset, doesn't really make sense with spring config case PresEffect.Lightspeed: return {renderDoc}; case PresEffect.None: @@ -1416,7 +1416,7 @@ export class DocumentView extends DocComponent() {
console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
, - { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc, + { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Expand } as any as Doc, this.Document )}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 69d03ac2e..101f28ae7 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -185,7 +185,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get currCPoints() { - const strPoints = this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'; + const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease'; return EaseFuncToPoints(strPoints); } @@ -858,9 +858,9 @@ export class PresBox extends ViewBoxBaseComponent() { effect: activeItem, noSelect: true, openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, - easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, + easeFunc: StrCast(activeItem.presentation_easeFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), - playAudio: BoolCast(activeItem.presPlayAudio), + playAudio: BoolCast(activeItem.presentation_playAudio), playMedia: activeItem.presentation_mediaStart === 'auto', }; if (activeItem.presentation_openInLightbox) { @@ -1575,16 +1575,16 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch updateEaseFunc = (activeItem: Doc) => { - activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; + activeItem.presentation_easeFunc = activeItem.presentation_easeFunc === 'linear' ? 'ease' : 'linear'; this.selectedArray.forEach(doc => { - doc.presEaseFunc = activeItem.presEaseFunc; + doc.presentation_easeFunc = activeItem.presentation_easeFunc; }); }; setEaseFunc = (activeItem: Doc, easeFunc: string) => { - activeItem.presEaseFunc = easeFunc; + activeItem.presentation_easeFunc = easeFunc; this.selectedArray.forEach(doc => { - doc.presEaseFunc = activeItem.presEaseFunc; + doc.presentation_easeFunc = activeItem.presentation_easeFunc; }); }; @@ -1602,9 +1602,9 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch updateEffectTiming = (activeItem: Doc, timing: SpringSettings) => { - activeItem.presEffectTiming = JSON.stringify(timing); + activeItem.presentation_effectTiming = JSON.stringify(timing); this.selectedArray.forEach(doc => { - doc.presEffectTiming = activeItem.presEffectTiming; + doc.presentation_effectTiming = activeItem.presentation_effectTiming; }); }; @@ -1829,7 +1829,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get transitionDropdown() { const { activeItem } = this; // Retrieving spring timing properties - const timing = StrCast(activeItem.presEffectTiming); + const timing = StrCast(activeItem.presentation_effectTiming); let timingConfig: SpringSettings | undefined; if (timing) { timingConfig = JSON.parse(timing); @@ -1847,8 +1847,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5; const zoom = NumCast(activeItem.config_zoom, 1) * 100; - const effect = StrCast(activeItem.presentation_effect) ? StrCast(activeItem.presentation_effect) : PresEffect.None; - const direction = StrCast(activeItem.presentation_effectDirection); + const effect = StrCast(activeItem.presentation_effect) ? (StrCast(activeItem.presentation_effect) as any as PresEffect) : PresEffect.None; + const direction = StrCast(activeItem.presentation_effectDirection) as PresEffectDirection; return ( <> @@ -1876,7 +1876,7 @@ export class PresBox extends ViewBoxBaseComponent() { /> } onClick={() => { @@ -1894,7 +1894,7 @@ export class PresBox extends ViewBoxBaseComponent() { type={Type.TERT} icon={this.isLoading ? : } iconPlacement="right" - color={StrCast(Doc.UserDoc().userVariantColor)} + color={SnappingManager.userVariantColor} onClick={() => { this.stopDictation(); this.customizeWithGPT(this.chatInput); @@ -1919,7 +1919,7 @@ export class PresBox extends ViewBoxBaseComponent() { > Movement () {
{/* Easing function */} { if (typeof val === 'string') { if (val !== 'custom') { @@ -2017,7 +2017,7 @@ export class PresBox extends ViewBoxBaseComponent() { type={Type.TERT} icon={this.isLoading ? : } iconPlacement="right" - color={StrCast(Doc.UserDoc().userVariantColor)} + color={SnappingManager.userVariantColor} onClick={this.customizeAnimations} />
@@ -2053,7 +2053,7 @@ export class PresBox extends ViewBoxBaseComponent() { mass: elem.mass, }); }}> - +
@@ -2062,7 +2062,7 @@ export class PresBox extends ViewBoxBaseComponent() {
{/* Effect dropdown */} () { {effect !== PresEffect.Lightspeed && ( <> () { Preview Effect
- +
@@ -2224,9 +2224,9 @@ export class PresBox extends ViewBoxBaseComponent() { { - activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio); + activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio); }} color={SnappingManager.userColor} /> @@ -2239,7 +2239,7 @@ export class PresBox extends ViewBoxBaseComponent() { }} color={SnappingManager.userColor} /> -
diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts index 564829d54..67cad9c5d 100644 --- a/src/client/views/nodes/trails/PresEnums.ts +++ b/src/client/views/nodes/trails/PresEnums.ts @@ -7,7 +7,7 @@ export enum PresMovement { } export enum PresEffect { - Zoom = 'Zoom', + Expand = 'Expand', Lightspeed = 'Lightspeed', Fade = 'Fade in', Flip = 'Flip', diff --git a/src/client/views/nodes/trails/SlideEffect.scss b/src/client/views/nodes/trails/SlideEffect.scss index cc851354e..aa2e5bbd9 100644 --- a/src/client/views/nodes/trails/SlideEffect.scss +++ b/src/client/views/nodes/trails/SlideEffect.scss @@ -8,7 +8,7 @@ .flip-side { position: absolute; will-change: transform, opacity; - backface-visibility: hidden; + // backface-visibility: hidden; } .flip-front { diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx index 03cd88f45..00039e3cb 100644 --- a/src/client/views/nodes/trails/SlideEffect.tsx +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -5,358 +5,116 @@ import { Doc } from '../../../../fields/Doc'; import { NumCast } from '../../../../fields/Types'; import { PresEffect, PresEffectDirection } from './PresEnums'; import './SlideEffect.scss'; +import { emptyFunction } from '../../../../Utils'; interface SlideEffectProps { - // pass in doc to extract width, height, bg - doc?: Doc; + doc?: Doc; // pass in doc to extract width, height, bg dir: PresEffectDirection; presEffect: PresEffect; - // stiffness (figma) = tension (react-spring) - tension: number; - // damping (figma) = friction (react-spring) - friction: number; - mass: number; + springSettings: { + stiffness: number; + damping: number; + mass: number; + }; children: React.ReactNode; infinite?: boolean; + startOpacity?: number; // set to zero to linearly fade in while animating } const DEFAULT_WIDTH = 40; const PREVIEW_OFFSET = 60; const ACTUAL_OFFSET = 200; -const infiniteOptions = { - loop: true, - delay: 500, -}; /** * This component wraps around the doc to create an effect animation, and also wraps the preview animations * for the effects as well. */ -export default function SpringAnimation({ doc, dir, friction, tension, mass, presEffect, children, infinite }: SlideEffectProps) { - const [springs, api] = useSpring( - () => ({ - from: { - x: 0, - y: 0, - opacity: 0, - scale: 1, - }, - config: { - tension, - friction, - mass, - }, - onStart: () => {}, - onRest: () => {}, - }), - [tension, friction, mass] - ); - const [ref, inView] = useInView({ - once: true, - }); - - const zoomConfig = { - from: { - scale: 0, - x: 0, - y: 0, - opacity: 1, - }, - to: { - scale: 1, - x: 0, - y: 0, - opacity: 1, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, +export default function SpringAnimation({ doc, dir, springSettings, presEffect, children, infinite, startOpacity }: SlideEffectProps) { + const expandConfig = { + to: { scale: 1, x: 0, y: 0 }, + from: { scale: 0, x: 0, y: 0 }, }; - const fadeConfig = { - from: { - opacity: 0, - scale: 1, - x: 0, - y: 0, - }, - to: { - opacity: 1, - scale: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 0, y: 0 }, + from: { x: 0, y: 0 }, }; - const rotateConfig = { - from: { - x: 0, - }, - to: { - x: 360, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 360, y: 0 }, + from: { x: 0, y: 0 }, }; - - const getBounceConfigFrom = () => { - switch (dir) { - case PresEffectDirection.Left: - return { - from: { - opacity: 0, - x: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, - y: 0, - }, - }; - case PresEffectDirection.Right: - return { - from: { - opacity: 0, - x: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, - y: 0, - }, - }; - case PresEffectDirection.Top: - return { - from: { - opacity: 0, - x: 0, - y: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, - }, - }; - case PresEffectDirection.Bottom: - return { - from: { - opacity: 0, - x: 0, - y: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, - }, - }; - default: - // no movement for center - return { - from: { - opacity: 0, - x: 0, - y: 0, - }, - }; - } - }; - - const bounceConfig = { - ...getBounceConfigFrom(), - to: [ - { - opacity: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, - ], - }; - const flipConfig = { - from: { - x: 0, - }, - to: { - x: 180, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 180, y: 0 }, + from: { x: 0, y: 0 }, }; - - // only left and right for now - const getRollConfigFrom = () => { - switch (dir) { - case PresEffectDirection.Left: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - case PresEffectDirection.Right: - return { - from: { - opacity: 0, - x: 100, - y: 120, - }, - }; - case PresEffectDirection.Top: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - case PresEffectDirection.Bottom: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - default: - // no movement for center - return { - from: { - opacity: 0, - x: 0, - y: 0, - }, - }; - } + const bounceConfig = { + to: { x: 0, y: 0 }, + from: (() => { + const offset = infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET; + switch (dir) { + case PresEffectDirection.Left: return { x: -offset, y: 0, }; + case PresEffectDirection.Right: return { x: offset, y: 0, }; + case PresEffectDirection.Top: return { x: 0, y: -offset, }; + case PresEffectDirection.Bottom:return { x: 0, y: offset, }; + default: return { x: 0, y: 0, }; // no movement for center + }})(), // prettier-ignore }; - const rollConfig = { - ...getRollConfigFrom(), - to: { - opacity: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 0, y: 0 }, + from: (() => { + switch (dir) { + case PresEffectDirection.Left: return { x: -100, y: -120, }; + case PresEffectDirection.Right: return { x: 100, y: 120, }; + case PresEffectDirection.Top: return { x: -100, y: -120, }; + case PresEffectDirection.Bottom: return { x: -100, y: -120, }; + default: return { x: 0, y: 0, }; // no movement for center + }})(), // prettier-ignore }; - // Switch animation depending on slide effect - const startAnimation = () => { - api.stop(); - let config: any = zoomConfig; + // prettier-ignore + const effectConfig = (() => { switch (presEffect) { - case PresEffect.Bounce: - config = bounceConfig; - break; - case PresEffect.Zoom: - config = zoomConfig; - break; - case PresEffect.Rotate: - config = rotateConfig; - break; - case PresEffect.Fade: - config = fadeConfig; - break; - case PresEffect.Flip: - config = flipConfig; - break; - case PresEffect.Roll: - config = rollConfig; - break; - case PresEffect.Lightspeed: - break; - default: - break; - } - - if (infinite) { - config = { ...config, ...infiniteOptions }; - } + case PresEffect.Fade: return fadeConfig; + case PresEffect.Bounce: return bounceConfig; + case PresEffect.Rotate: return rotateConfig; + case PresEffect.Flip: return flipConfig; + case PresEffect.Roll: return rollConfig; + case PresEffect.Lightspeed: return { from: {}, to: {} }; + case PresEffect.Expand: + default: return expandConfig; + } // prettier-ignore + })(); - api.start(config); - }; - - const getRenderDoc = () => { - switch (presEffect) { - case PresEffect.Rotate: - return ( - `rotate(${val}deg)`) }}> - {children} - - ); - case PresEffect.Flip: - return ( - // Pass in doc dimensions -
- {dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? ( - <> - `perspective(600px) rotateX(${val}deg)`), - width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, - height: doc ? NumCast(doc.height) : DEFAULT_WIDTH, - backgroundColor: infinite ? '#a825ff' : 'rgb(223, 223, 223);', - }} - /> - `perspective(600px) rotateX(${val}deg)`), rotateX: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> - {children} - - - ) : ( - <> - `perspective(600px) rotateY(${val}deg)`), width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }} - /> - `perspective(600px) rotateY(${val}deg)`), rotateY: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> - {children} - - - )} -
- ); - case PresEffect.Roll: - return ( - `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }}> - {children} - - ); - default: - return ( - - {children} - - ); - } - }; - - useEffect(() => { - if (infinite || !inView) return; - setTimeout(() => { - startAnimation(); - }, 100); - }, [inView]); + const [springs, api] = useSpring( + () => ({ + to: { ...effectConfig.to, opacity: 1 }, + from: { ...effectConfig.from, opacity: startOpacity ?? 1 }, + config: { tension: springSettings.stiffness, friction: springSettings.damping, mass: springSettings.mass }, + onStart: emptyFunction, + onRest: emptyFunction, + }), + [springSettings] + ); + const [ref, inView] = useInView({ + once: true, + }); useEffect(() => { - if (infinite) { - startAnimation(); + if (inView) { + api.start({ loop: infinite, delay: infinite ? 500 : 0 }); } - }, [presEffect, tension, friction, mass]); - - return
{getRenderDoc()}
; + }, [inView]); + const animatedDiv = (style: any) => ( + `${val}`) }}> + {children} + + ); + const [width, height] = [NumCast(doc?.width, DEFAULT_WIDTH), NumCast(doc?.height, DEFAULT_WIDTH)]; + const flipAxis = dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? 'X' : 'Y'; + const [rotateX, rotateY] = flipAxis === 'X' ? ['180deg', undefined] : [undefined, '180deg']; + switch (presEffect) { + case PresEffect.Flip: return animatedDiv({ transform: to(springs.x, val => `perspective(600px) rotate${flipAxis}(${val}deg)`), width, height, rotateX, rotateY }) + case PresEffect.Rotate:return animatedDiv({ transform: to(springs.x, val => `rotate(${val}deg)`) }); + case PresEffect.Roll: return animatedDiv({ transform: to([springs.x, springs.y], (val, val2) => `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }); + default: return animatedDiv(springs); + } // prettier-ignore } diff --git a/src/client/views/nodes/trails/SpringUtils.ts b/src/client/views/nodes/trails/SpringUtils.ts index bfb22c46a..73e1e14f1 100644 --- a/src/client/views/nodes/trails/SpringUtils.ts +++ b/src/client/views/nodes/trails/SpringUtils.ts @@ -80,7 +80,7 @@ export const effectItems = Object.values(PresEffect) export const presEffectDefaultTimings: { [key: string]: SpringSettings; } = { - Zoom: { type: SpringType.GENTLE, stiffness: 100, damping: 15, mass: 1 }, + Expand: { type: SpringType.GENTLE, stiffness: 100, damping: 15, mass: 1 }, Bounce: { type: SpringType.BOUNCY, stiffness: 600, -- cgit v1.2.3-70-g09d2 From 38a382a03675d6a50ec7de75f05025efd093f570 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 May 2024 10:39:25 -0400 Subject: manually added ChatBox from aj-starter --- package-lock.json | 359 ++++++++++++ package.json | 7 + src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 + src/client/util/CurrentUserUtils.ts | 2 + src/client/views/Main.tsx | 2 + src/client/views/nodes/ChatBox/ChatBox.scss | 228 ++++++++ src/client/views/nodes/ChatBox/ChatBox.tsx | 609 +++++++++++++++++++++ .../views/nodes/ChatBox/MessageComponent.tsx | 116 ++++ src/client/views/nodes/ChatBox/types.ts | 23 + src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/fields/Types.ts | 5 +- src/server/ApiManagers/AssistantManager.ts | 131 +++++ src/server/index.ts | 10 +- 14 files changed, 1498 insertions(+), 6 deletions(-) create mode 100644 src/client/views/nodes/ChatBox/ChatBox.scss create mode 100644 src/client/views/nodes/ChatBox/ChatBox.tsx create mode 100644 src/client/views/nodes/ChatBox/MessageComponent.tsx create mode 100644 src/client/views/nodes/ChatBox/types.ts create mode 100644 src/server/ApiManagers/AssistantManager.ts (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index 60198132c..35ffc712c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -86,6 +87,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -107,6 +109,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -133,6 +136,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -182,6 +186,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -193,8 +198,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", @@ -9347,6 +9354,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, "node_modules/@types/keygrip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", @@ -11251,6 +11263,17 @@ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" }, + "node_modules/better-react-mathjax": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz", + "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==", + "dependencies": { + "mathjax-full": "^3.2.2" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/bezier-curve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", @@ -15907,6 +15930,33 @@ "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz", "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag==" }, + "node_modules/csvtojson": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", + "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "dependencies": { + "bluebird": "^3.5.1", + "lodash": "^4.17.3", + "strip-bom": "^2.0.0" + }, + "bin": { + "csvtojson": "bin/csvtojson" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/csvtojson/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -18913,6 +18963,14 @@ "node": ">=8" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -20825,6 +20883,52 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", @@ -20844,6 +20948,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -20924,6 +21040,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -22592,6 +22723,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -23214,6 +23350,29 @@ "node": ">=12.0.0" } }, + "node_modules/katex": { + "version": "0.16.10", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", + "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", @@ -23429,6 +23588,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -23796,6 +23963,27 @@ "vt-pbf": "^3.1.3" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -23819,6 +24007,17 @@ "mr-parser": "^0.2.1" } }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, "node_modules/mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", @@ -23987,6 +24186,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", @@ -24109,6 +24326,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -24172,6 +24394,11 @@ "node": ">= 0.6" } }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" + }, "node_modules/micromark": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", @@ -24353,6 +24580,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz", + "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", @@ -24840,6 +25085,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -29576,6 +29826,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -30099,6 +30357,22 @@ "@types/react": "^17" } }, + "node_modules/react-latex-next": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz", + "integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==", + "dependencies": { + "katex": "^0.16.0" + }, + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-loading": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz", @@ -30622,6 +30896,24 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-katex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz", + "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -30661,6 +30953,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -32129,6 +32436,27 @@ "node": ">= 6" } }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/splaytree": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", @@ -34036,6 +34364,11 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -34164,6 +34497,19 @@ "node": ">=0.10.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -35165,6 +35511,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -35481,6 +35832,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "engines": { + "node": ">=0.1" + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", diff --git a/package.json b/package.json index 833bebf44..aa0874714 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -171,6 +172,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -192,6 +194,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -218,6 +221,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -267,6 +271,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -278,8 +283,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index b4ad9c17d..0520dc375 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -20,6 +20,7 @@ export enum DocumentType { WEBCAM = 'webcam', // webcam CONFIG = 'config', // configuration document intended to specify a view layout configuration, but not be directly rendered (e.g., for saving the page# of a PDF, or view transform of a collection) SCRIPTING = 'script', // script editor + CHAT = 'chat', // chat with GPT about files EQUATION = 'equation', // equation editor FUNCPLOT = 'funcplot', // function plotter MAP = 'map', diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 24dd5cf37..ef1c709f0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -180,6 +180,12 @@ export class DocumentOptions { date_range?: STRt = new StrInfo('date range for calendar', false); + chat?: STRt = new StrInfo('fields related to chatBox', false); + chat_history?: STRt = new StrInfo('chat history for chatbox', false); + chat_thread_id?: STRt = new StrInfo('thread id for chatbox', false); + chat_assistant_id?: STRt = new StrInfo('assistant id for chatbox', false); + chat_vector_store_id?: STRt = new StrInfo('assistant id for chatbox', false); + wikiData?: STRt = new StrInfo('WikiData ID related to map location'); description?: STRt = new StrInfo('description of document'); _timecodeToShow?: NUMt = new NumInfo('media timecode when document should appear (e.g., when an annotation shows up as a video plays)', false); @@ -735,6 +741,9 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `` /* ScriptingBox.LayoutString(fieldKey) */ : undefined }); } + export function ChatDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.CHAT), undefined, { ...(options || {}) }); + } // eslint-disable-next-line default-param-last export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ffc1304bf..09250d29d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -368,6 +368,7 @@ pie title Minerals in my tap water {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, + {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 300, _height: 300, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, @@ -397,6 +398,7 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, + { toolTip: "Tap or drag to create a chat assistant", title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 8968acbbb..149de59b2 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -28,6 +28,7 @@ import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; import './global/globalScripts'; import { AudioBox } from './nodes/AudioBox'; import { ComparisonBox } from './nodes/ComparisonBox'; +import { ChatBox } from './nodes/ChatBox/ChatBox'; import { DataVizBox } from './nodes/DataVizBox/DataVizBox'; import { DocumentContentsView, HTMLtag } from './nodes/DocumentContentsView'; import { EquationBox } from './nodes/EquationBox'; @@ -136,6 +137,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; MapBox, ScreenshotBox, DataVizBox, + ChatBox, HTMLtag, ComparisonBox, LoadingBox, diff --git a/src/client/views/nodes/ChatBox/ChatBox.scss b/src/client/views/nodes/ChatBox/ChatBox.scss new file mode 100644 index 000000000..f1ad3d074 --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.scss @@ -0,0 +1,228 @@ +$background-color: #f8f9fa; +$text-color: #333; +$input-background: #fff; +$button-color: #007bff; +$button-hover-color: darken($button-color, 10%); +$shadow-color: rgba(0, 0, 0, 0.075); +$border-radius: 8px; + +.chatBox { + display: flex; + flex-direction: column; + width: 100%; /* Adjust the width as needed, could be in percentage */ + height: 100%; /* Adjust the height as needed, could be in percentage */ + background-color: $background-color; + font-family: 'Helvetica Neue', Arial, sans-serif; + //margin: 20px auto; + //overflow: hidden; + + .scroll-box { + flex-grow: 1; + overflow-y: scroll; + overflow-x: hidden; + height: 100%; + padding: 10px; + display: flex; + flex-direction: column-reverse; + + &::-webkit-scrollbar { + width: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: darken($background-color, 10%); + border-radius: $border-radius; + } + + + .chat-content { + display: flex; + flex-direction: column; + } + + .messages { + display: flex; + flex-direction: column; + .message { + padding: 10px; + margin-bottom: 10px; + border-radius: $border-radius; + background-color: lighten($background-color, 5%); + box-shadow: 0 2px 5px $shadow-color; + //display: flex; + align-items: center; + max-width: 70%; + word-break: break-word; + .message-footer { // Assuming this is the container for the toggle button + //max-width: 70%; + + + .toggle-logs-button { + margin-top: 10px; // Padding on sides to align with the text above + width: 95%; + //display: block; // Ensures the button extends the full width of its container + text-align: center; // Centers the text inside the button + //padding: 8px 0; // Adequate padding for touch targets + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + //transition: background-color 0.3s; + //margin-top: 10px; // Adds space above the button + box-shadow: 0 2px 4px $shadow-color; // Consistent shadow with other elements + &:hover { + background-color: $button-hover-color; + } + } + .tool-logs { + width: 100%; + background-color: $input-background; + color: $text-color; + margin-top: 5px; + //padding: 10px; + //border-radius: $border-radius; + //box-shadow: inset 0 2px 4px $shadow-color; + //transition: opacity 1s ease-in-out; + font-family: monospace; + overflow-x: auto; + max-height: 150px; // Ensuring it does not grow too large + overflow-y: auto; + } + + } + + .custom-link { + color: lightblue; + text-decoration: underline; + cursor: pointer; + } + &.user { + align-self: flex-end; + background-color: $button-color; + color: #fff; + } + + &.chatbot { + align-self: flex-start; + background-color: $input-background; + color: $text-color; + } + + span { + flex-grow: 1; + padding-right: 10px; + } + + img { + max-width: 50px; + max-height: 50px; + border-radius: 50%; + } + } + } + padding-bottom: 0; + } + + .chat-form { + display: flex; + flex-grow: 1; + //height: 50px; + bottom: 0; + width: 100%; + padding: 10px; + background-color: $input-background; + box-shadow: inset 0 -1px 2px $shadow-color; + + input[type="text"] { + flex-grow: 1; + border: 1px solid darken($input-background, 10%); + border-radius: $border-radius; + padding: 8px 12px; + margin-right: 10px; + } + + button { + padding: 8px 16px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + margin-bottom: 0; + } +} + +.initializing-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba($background-color, 0.95); + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5em; + color: $text-color; + z-index: 10; // Ensure it's above all other content (may be better solution) + + &::before { + content: 'Initializing...'; + font-weight: bold; + } +} + + +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.4); + + .modal-content { + background-color: $input-background; + color: $text-color; + padding: 20px; + border-radius: $border-radius; + box-shadow: 0 2px 10px $shadow-color; + display: flex; + flex-direction: column; + align-items: center; + width: auto; + min-width: 300px; + + h4 { + margin-bottom: 15px; + } + + p { + margin-bottom: 20px; + } + + button { + padding: 10px 20px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + margin: 5px; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + } +} diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx new file mode 100644 index 000000000..880c332ac --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.tsx @@ -0,0 +1,609 @@ +import { MathJaxContext } from 'better-react-mathjax'; +import { action, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import OpenAI, { ClientOptions } from 'openai'; +import { ImageFile, Message } from 'openai/resources/beta/threads/messages'; +import { RunStep } from 'openai/resources/beta/threads/runs/steps'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types'; +import { CsvField } from '../../../../fields/URLField'; +import { Networking } from '../../../Network'; +import { DocUtils } from '../../../documents/DocUtils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { LinkManager } from '../../../util/LinkManager'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { FieldView, FieldViewProps } from '../FieldView'; +import './ChatBox.scss'; +import MessageComponent from './MessageComponent'; +import { ANNOTATION_LINK_TYPE, ASSISTANT_ROLE, AssistantMessage, DOWNLOAD_TYPE } from './types'; + +@observer +export class ChatBox extends ViewBoxAnnotatableComponent() { + @observable modalStatus = false; + @observable currentFile = { url: '' }; + @observable history: AssistantMessage[] = []; + @observable.deep current_message: AssistantMessage | undefined = undefined; + + @observable isLoading: boolean = false; + @observable isInitializing: boolean = true; + @observable expandedLogIndex: number | null = null; + @observable linked_docs_to_add: Doc[] = []; + + private openai: OpenAI; + private interim_history: string = ''; + private assistantID: string = ''; + private threadID: string = ''; + private _oldWheel: any; + private vectorStoreID: string = ''; + private mathJaxConfig: any; + private linkedCsvIDs: string[] = []; + + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ChatBox, fieldKey); + } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + this.openai = this.initializeOpenAI(); + this.history = []; + this.threadID = StrCast(this.dataDoc.thread_id); + this.assistantID = StrCast(this.dataDoc.assistant_id); + this.vectorStoreID = StrCast(this.dataDoc.vector_store_id); + this.openai = this.initializeOpenAI(); + if (this.assistantID === '' || this.threadID === '' || this.vectorStoreID === '') { + this.createAssistant(); + } else { + this.retrieveCsvUrls(); + this.isInitializing = false; + } + this.mathJaxConfig = { + loader: { load: ['input/asciimath'] }, + tex: { + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + displayMath: [ + ['$$', '$$'], + ['[', ']'], + ], + }, + }; + reaction( + () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, image: msg.image, tool_logs: msg.tool_logs, links: msg.links })), + serializableHistory => { + this.dataDoc.data = JSON.stringify(serializableHistory); + } + ); + } + + toggleToolLogs = (index: number) => { + this.expandedLogIndex = this.expandedLogIndex === index ? null : index; + }; + + retrieveCsvUrls() { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + linkedDocs.forEach(doc => { + const aiFieldId = StrCast(doc[this.Document[Id] + '_ai_field_id']); + if (CsvCast(doc.data)) { + this.linkedCsvIDs.push(StrCast(aiFieldId)); + console.log(this.linkedCsvIDs); + } + }); + } + + initializeOpenAI() { + const configuration: ClientOptions = { + apiKey: process.env.OPENAI_KEY, + dangerouslyAllowBrowser: true, + }; + return new OpenAI(configuration); + } + + onPassiveWheel = (e: WheelEvent) => { + if (this._props.isContentActive()) { + e.stopPropagation(); + } + }; + + createLink = (linkInfo: string, startIndex: number, endIndex: number, linkType: ANNOTATION_LINK_TYPE, annotationIndex: number = 0) => { + const text = this.interim_history; + const subString = this.current_message?.text.substring(startIndex, endIndex) ?? ''; + if (!text) return; + const textToDisplay = `${annotationIndex}`; + let fileInfo = linkInfo; + const fileName = subString.split('/')[subString.split('/').length - 1]; + if (linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE) { + fileInfo = linkInfo + '!!!' + fileName; + } + + const formattedLink = `[${textToDisplay}](${fileInfo}~~~${linkType})`; + console.log(formattedLink); + const newText = text.replace(subString, formattedLink); + runInAction(() => { + this.interim_history = newText; + console.log(newText); + this.current_message?.links?.push({ + start: startIndex, + end: endIndex, + url: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? fileName : linkInfo, + id: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? linkInfo : undefined, + link_type: linkType, + }); + }); + }; + + @action + createAssistant = async () => { + this.isInitializing = true; + try { + const vectorStore = await this.openai.beta.vectorStores.create({ + name: 'Vector Store for Assistant', + }); + const assistant = await this.openai.beta.assistants.create({ + name: 'Document Analyser Assistant', + instructions: ` + You will analyse documents with which you are provided. You will answer questions and provide insights based on the information in the documents. + For writing math formulas: + You have a MathJax render environment. + - Write all in-line equations within a single dollar sign, $, to render them as TeX (this means any time you want to use a dollar sign to represent a dollar sign itself, you must escape it with a backslash: "$"); + - Use a double dollar sign, $$, to render equations on a new line; + Example: $$x^2 + 3x$$ is output for "x² + 3x" to appear as TeX.`, + model: 'gpt-4-turbo', + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [vectorStore.id], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + const thread = await this.openai.beta.threads.create(); + + runInAction(() => { + this.dataDoc.assistant_id = assistant.id; + this.dataDoc.thread_id = thread.id; + this.dataDoc.vector_store_id = vectorStore.id; + this.assistantID = assistant.id; + this.threadID = thread.id; + this.vectorStoreID = vectorStore.id; + this.isInitializing = false; + }); + } catch (error) { + console.error('Initialization failed:', error); + this.isInitializing = false; + } + }; + + @action + runAssistant = async (inputText: string) => { + // Ensure an assistant and thread are created + if (!this.assistantID || !this.threadID || !this.vectorStoreID) { + await this.createAssistant(); + console.log('Assistant and thread created:', this.assistantID, this.threadID); + } + let currentText: string = ''; + let currentToolCallMessage: string = ''; + + // Send the user's input to the assistant + await this.openai.beta.threads.messages.create(this.threadID, { + role: 'user', + content: inputText, + }); + + // Listen to the streaming responses + const stream = this.openai.beta.threads.runs + .stream(this.threadID, { + assistant_id: this.assistantID, + }) + .on('runStepCreated', (runStep: RunStep) => { + currentText = ''; + runInAction(() => { + this.current_message = { role: ASSISTANT_ROLE.ASSISTANT, text: currentText, tool_logs: '', links: [] }; + }); + this.isLoading = true; + }) + .on('toolCallDelta', (toolCallDelta, snapshot) => { + this.isLoading = false; + if (toolCallDelta.type === 'code_interpreter') { + if (toolCallDelta.code_interpreter?.input) { + currentToolCallMessage += toolCallDelta.code_interpreter.input; + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + } + if (toolCallDelta.code_interpreter?.outputs) { + currentToolCallMessage += '\n Code interpreter output:'; + toolCallDelta.code_interpreter.outputs.forEach(output => { + if (output.type === 'logs') { + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs += '\n|' + output.logs; + } + }); + } + }); + } + } + }) + .on('textDelta', (textDelta, snapshot) => { + this.isLoading = false; + currentText += textDelta.value; + runInAction(() => { + if (this.current_message) { + // this.current_message = {...this.current_message, text: current_text}; + this.current_message.text = currentText; + } + }); + }) + .on('messageDone', async event => { + console.log(event); + const textItem = event.content.find(item => item.type === 'text'); + if (textItem && textItem.type === 'text') { + const { text } = textItem; + console.log(text.value); + try { + runInAction(() => { + this.interim_history = text.value; + }); + } catch (e) { + console.error('Error parsing JSON response:', e); + } + + const { annotations } = text; + console.log('Annotations: ' + annotations); + let index = 0; + annotations.forEach(async annotation => { + console.log(' ' + annotation); + console.log(' ' + annotation.text); + if (annotation.type === 'file_path') { + const { file_path: filePath } = annotation; + const fileToDownload = filePath.file_id; + console.log(fileToDownload); + if (filePath) { + console.log(filePath); + console.log(fileToDownload); + this.createLink(fileToDownload, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DOWNLOAD_FILE); + } + } else { + const { file_citation: fileCitation } = annotation; + if (fileCitation) { + const citedFile = await this.openai.files.retrieve(fileCitation.file_id); + const citationUrl = citedFile.filename; + this.createLink(citationUrl, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DASH_DOC, index); + index++; + } + } + }); + runInAction(() => { + if (this.current_message) { + console.log('current message: ' + this.current_message.text); + this.current_message.text = this.interim_history; + this.history.push({ ...this.current_message }); + this.current_message = undefined; + } + }); + } + }) + .on('toolCallDone', toolCall => { + runInAction(() => { + if (this.current_message && currentToolCallMessage) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + }) + .on('imageFileDone', (content: ImageFile, snapshot: Message) => { + console.log('Image file done:', content); + }) + .on('end', () => { + console.log('Streaming done'); + }); + }; + + @action + goToLinkedDoc = async (link: string) => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + const linkedDoc = linkedDocs.find(doc => { + const docUrl = CsvCast(doc.data, PDFCast(doc.data)).url.pathname.replace('/files/pdfs/', '').replace('/files/csvs/', ''); + console.log('URL: ' + docUrl + ' Citation URL: ' + link); + return link === docUrl; + }); + + if (linkedDoc) { + await DocumentManager.Instance.showDocument(DocCast(linkedDoc), { willZoomCentered: true }, () => {}); + } + }; + + @action + askGPT = async (event: React.FormEvent): Promise => { + event.preventDefault(); + + const textInput = event.currentTarget.elements.namedItem('messageInput') as HTMLInputElement; + const trimmedText = textInput.value.trim(); + + if (!this.assistantID || !this.threadID) { + try { + await this.createAssistant(); + } catch (err) { + console.error('Error:', err); + } + } + + if (trimmedText) { + try { + textInput.value = ''; + runInAction(() => { + this.history.push({ role: ASSISTANT_ROLE.USER, text: trimmedText }); + }); + await this.runAssistant(trimmedText); + this.dataDoc.data = this.history.toString(); + } catch (err) { + console.error('Error:', err); + } + } + }; + + @action + uploadLinks = async (linkedDocs: Doc[]) => { + if (this.isInitializing) { + console.log('Initialization in progress, upload aborted.'); + return; + } + const urls = linkedDocs.map(doc => CsvCast(doc.data, PDFCast(doc.data)).url.pathname); + const csvUrls = urls.filter(url => url.endsWith('.csv')); + console.log(this.assistantID, this.threadID, urls); + + const { openai_file_ids: openaiFileIds } = await Networking.PostToServer('/uploadPDFToVectorStore', { urls, threadID: this.threadID, assistantID: this.assistantID, vector_store_id: this.vectorStoreID }); + + linkedDocs.forEach((doc, i) => { + doc[this.Document[Id] + '_ai_field_id'] = openaiFileIds[i]; + console.log('AI Field ID: ' + openaiFileIds[i]); + }); + + if (csvUrls.length > 0) { + for (let i = 0; i < csvUrls.length; i++) { + this.linkedCsvIDs.push(openaiFileIds[urls.indexOf(csvUrls[i])]); + } + console.log('linked csvs:' + this.linkedCsvIDs); + await this.openai.beta.assistants.update(this.assistantID, { + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [this.vectorStoreID], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + } + }; + + downloadToComputer = (url: string, fileName: string) => { + fetch(url, { method: 'get', mode: 'no-cors', referrerPolicy: 'no-referrer' }) + .then(res => res.blob()) + .then(res => { + const aElement = document.createElement('a'); + aElement.setAttribute('download', fileName); + const href = URL.createObjectURL(res); + aElement.href = href; + aElement.setAttribute('target', '_blank'); + aElement.click(); + URL.revokeObjectURL(href); + }); + }; + + createDocumentInDash = async (url: string) => { + const fileSuffix = url.substring(url.lastIndexOf('.') + 1); + console.log(fileSuffix); + let doc: Doc | null = null; + switch (fileSuffix) { + case 'pdf': + doc = DocCast(await DocUtils.DocumentFromType('pdf', url, {})); + break; + case 'csv': + doc = DocCast(await DocUtils.DocumentFromType('csv', url, {})); + break; + case 'png': + case 'jpg': + case 'jpeg': + doc = DocCast(await DocUtils.DocumentFromType('image', url, {})); + break; + default: + console.error('Unsupported file type:', fileSuffix); + break; + } + if (doc) { + doc && this._props.addDocument?.(doc); + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); + } + }; + + downloadFile = async (fileInfo: string, downloadType: DOWNLOAD_TYPE) => { + try { + console.log(fileInfo); + const [fileId, fileName] = fileInfo.split(/!!!/); + const { file_path: filePath } = await Networking.PostToServer('/downloadFileFromOpenAI', { file_id: fileId, file_name: fileName }); + const fileLink = CsvCast(new CsvField(filePath)).url.href; + if (downloadType === DOWNLOAD_TYPE.DASH) { + this.createDocumentInDash(fileLink); + } else { + this.downloadToComputer(fileLink, fileName); + } + } catch (error) { + console.error('Error downloading file:', error); + } + }; + + handleDownloadToDevice = () => { + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DEVICE); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + handleAddToDash = () => { + // Assuming `downloadFile` is a method that handles adding to Dash + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DASH); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + renderModal = () => { + if (!this.modalStatus) return null; + + return ( +
+
+

File Actions

+

Choose an action for the file:

+ + + +
+
+ ); + }; + @action + showModal = () => { + this.modalStatus = true; + }; + + @action + setCurrentFile = (file: { url: string }) => { + this.currentFile = file; + }; + + componentDidMount() { + this._props.setContentViewBox?.(this); + if (this.dataDoc.data) { + try { + const storedHistory = JSON.parse(StrCast(this.dataDoc.data)); + runInAction(() => { + this.history = storedHistory.map((msg: AssistantMessage) => ({ + role: msg.role, + text: msg.text, + quote: msg.quote, + tool_logs: msg.tool_logs, + image: msg.image, + })); + }); + } catch (e) { + console.error('Failed to parse history from dataDoc:', e); + } + } + reaction( + () => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + return linkedDocs; + }, + + linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc))) + ); + + observe( + // right now this skips during initialization which is necessary because it would be blank + // However, it will upload the same link twice when it is + this.linked_docs_to_add, + change => { + // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) + switch (change.type as any) { + case 'splice': + if ((change as any).addedCount > 0) { + // maybe check here if its already in the urls datadoc array so doesn't add twice + console.log((change as any).added as Doc[]); + this.uploadLinks((change as any).added as Doc[]); + } + // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; + case 'update': // let oldValue = change.oldValue; + default: + } + }, + true + ); + } + + render() { + return ( + +
+ {this.isInitializing &&
Initializing...
} + {this.renderModal()} +
{ + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }}> +
+ {this.history.map((message, index) => ( + + ))} + {!this.current_message ? null : ( + + )} +
+
+
+ + +
+
+
+ ); + } +} + +Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, { + layout: { view: ChatBox, dataField: 'data' }, + options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, +}); diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx new file mode 100644 index 000000000..fced0b4d5 --- /dev/null +++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import { observer } from 'mobx-react'; +import { MathJax, MathJaxContext } from 'better-react-mathjax'; +import ReactMarkdown from 'react-markdown'; +import { TbCircle0Filled, TbCircle1Filled, TbCircle2Filled, TbCircle3Filled, TbCircle4Filled, TbCircle5Filled, TbCircle6Filled, TbCircle7Filled, TbCircle8Filled, TbCircle9Filled } from 'react-icons/tb'; +import { AssistantMessage } from './types'; + +interface MessageComponentProps { + message: AssistantMessage; + toggleToolLogs: (index: number) => void; + expandedLogIndex: number | null; + index: number; + showModal: () => void; + goToLinkedDoc: (url: string) => void; + setCurrentFile: (file: { url: string }) => void; + isCurrent?: boolean; +} + +const MessageComponent: React.FC = function ({ message, toggleToolLogs, expandedLogIndex, goToLinkedDoc, index, showModal, setCurrentFile, isCurrent = false }) { + // const messageClass = `${message.role} ${isCurrent ? 'current-message' : ''}`; + + const LinkRenderer = ({ href, children }: { href: string; children: React.ReactNode }) => { + // console.log(href + " " + children) + const regex = /([a-zA-Z0-9_.!-]+)~~~(citation|file_path)/; + const matches = href.match(regex); + // console.log(href) + // console.log(matches) + const url = matches ? matches[1] : href; + const linkType = matches ? matches[2] : null; + if (linkType === 'citation') { + switch (children) { + case '0': + children = ; + break; + case '1': + children = ; + break; + case '2': + children = ; + break; + case '3': + children = ; + break; + case '4': + children = ; + break; + case '5': + children = ; + break; + case '6': + children = ; + break; + case '7': + children = ; + break; + case '8': + children = ; + break; + case '9': + children = ; + break; + default: + break; + } + } + // console.log(linkType) + const style = { + color: 'lightblue', + verticalAlign: linkType === 'citation' ? 'super' : 'baseline', + fontSize: linkType === 'citation' ? 'smaller' : 'inherit', + }; + + return ( + { + e.preventDefault(); + if (linkType === 'citation') { + goToLinkedDoc(url); + } else if (linkType === 'file_path') { + showModal(); + setCurrentFile({ url }); + } + }} + style={style}> + {children} + + ); + }; + + return ( +
+ + + {message.text ? message.text : ''} + + + {message.image && } +
+ {message.tool_logs && ( + + )} + {expandedLogIndex === index && ( +
+
{message.tool_logs}
+
+ )} +
+
+ ); +}; + +export default observer(MessageComponent); diff --git a/src/client/views/nodes/ChatBox/types.ts b/src/client/views/nodes/ChatBox/types.ts new file mode 100644 index 000000000..8212a7050 --- /dev/null +++ b/src/client/views/nodes/ChatBox/types.ts @@ -0,0 +1,23 @@ +export enum ASSISTANT_ROLE { + USER = 'User', + ASSISTANT = 'Assistant', +} + +export enum ANNOTATION_LINK_TYPE { + DASH_DOC = 'citation', + DOWNLOAD_FILE = 'file_path', +} + +export enum DOWNLOAD_TYPE { + DASH = 'dash', + DEVICE = 'device', +} + +export interface AssistantMessage { + role: ASSISTANT_ROLE; + text: string; + quote?: string; + image?: string; + tool_logs?: string; + links?: { start: number; end: number; url: string; id?: string; link_type: ANNOTATION_LINK_TYPE }[]; +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 18529a429..192c7875e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -79,7 +79,7 @@ export class DocumentContentsView extends ObservableReactComponent = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List ? ListSpec : new (...args: any[]) => T; @@ -122,6 +122,9 @@ export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } +export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) { + return Cast(field, PdfField, defaultVal); +} export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } diff --git a/src/server/ApiManagers/AssistantManager.ts b/src/server/ApiManagers/AssistantManager.ts new file mode 100644 index 000000000..82e48167a --- /dev/null +++ b/src/server/ApiManagers/AssistantManager.ts @@ -0,0 +1,131 @@ +import * as fs from 'fs'; +import { createReadStream, writeFile } from 'fs'; +import OpenAI from 'openai'; +import * as path from 'path'; +import { promisify } from 'util'; +import * as uuid from 'uuid'; +import { filesDirectory, publicDirectory } from '..'; +import { Method } from '../RouteManager'; +import ApiManager, { Registration } from './ApiManager'; + +export enum Directory { + parsed_files = 'parsed_files', + images = 'images', + videos = 'videos', + pdfs = 'pdfs', + text = 'text', + pdf_thumbnails = 'pdf_thumbnails', + audio = 'audio', + csv = 'csv', +} + +export function serverPathToFile(directory: Directory, filename: string) { + return path.normalize(`${filesDirectory}/${directory}/${filename}`); +} + +export function pathToDirectory(directory: Directory) { + return path.normalize(`${filesDirectory}/${directory}`); +} + +export function clientPathToFile(directory: Directory, filename: string) { + return `/files/${directory}/${filename}`; +} + +const writeFileAsync = promisify(writeFile); +const readFileAsync = promisify(fs.readFile); + +export default class AssistantManager extends ApiManager { + protected initialize(register: Registration): void { + const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true }); + + register({ + method: Method.POST, + subscription: '/uploadPDFToVectorStore', + secureHandler: async ({ req, res }) => { + const { urls, threadID, assistantID, vector_store_id } = req.body; + + const csvFilesIds: string[] = []; + const otherFileIds: string[] = []; + const allFileIds: string[] = []; + + const fileProcesses = urls.map(async (source: string) => { + const fullPath = path.join(publicDirectory, source); + const fileData = await openai.files.create({ file: createReadStream(fullPath), purpose: 'assistants' }); + allFileIds.push(fileData.id); + if (source.endsWith('.csv')) { + console.log(source); + csvFilesIds.push(fileData.id); + } else { + openai.beta.vectorStores.files.create(vector_store_id, { file_id: fileData.id }); + otherFileIds.push(fileData.id); + } + }); + try { + await Promise.all(fileProcesses).then(() => { + res.send({ vector_store_id: vector_store_id, openai_file_ids: allFileIds }); + }); + } catch (error) { + res.status(500).send({ error: 'Failed to process files' + error }); + } + }, + }); + + register({ + method: Method.POST, + subscription: '/downloadFileFromOpenAI', + secureHandler: async ({ req, res }) => { + const { file_id, file_name } = req.body; + //let files_directory: string; + let files_directory = '/files/openAIFiles/'; + switch (file_name.split('.').pop()) { + case 'pdf': + files_directory = '/files/pdfs/'; + break; + case 'csv': + files_directory = '/files/csv/'; + break; + case 'png': + case 'jpg': + case 'jpeg': + files_directory = '/files/images/'; + break; + default: + break; + } + + const directory = path.join(publicDirectory, files_directory); + + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + const file = await openai.files.content(file_id); + const new_file_name = `${uuid.v4()}-${file_name}`; + const file_path = path.join(directory, new_file_name); + const file_array_buffer = await file.arrayBuffer(); + const bufferView = new Uint8Array(file_array_buffer); + try { + const written_file = await writeFileAsync(file_path, bufferView); + console.log(written_file); + console.log(file_path); + console.log(file_array_buffer); + console.log(bufferView); + const file_object = new File([bufferView], file_name); + //DashUploadUtils.upload(file_object, 'openAIFiles'); + res.send({ file_path: path.join(files_directory, new_file_name) }); + /* res.send( { + source: "file", + result: { + accessPaths: { + agnostic: {client: path.join('/files/openAIFiles/', `${uuid.v4()}-${file_name}`)} + }, + rawText: "", + duration: 0, + }, + } ); */ + } catch (error) { + res.status(500).send({ error: 'Failed to write file' + error }); + } + }, + }); + } +} diff --git a/src/server/index.ts b/src/server/index.ts index 1bbf8a105..3151c2975 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,7 +3,7 @@ import * as dotenv from 'dotenv'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import { logExecution } from './ActionUtilities'; -import { AdminPrivileges, resolvedPorts } from './SocketData'; +import AssistantManager from './ApiManagers/AssistantManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; @@ -13,16 +13,17 @@ import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; import UserManager from './ApiManagers/UserManager'; import UtilManager from './ApiManagers/UtilManager'; -import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; -import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; import { DashSessionAgent } from './DashSession/DashSessionAgent'; import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent'; import { DashStats } from './DashStats'; import { DashUploadUtils } from './DashUploadUtils'; -import { Database } from './database'; import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; +import { AdminPrivileges, resolvedPorts } from './SocketData'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; +import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; +import { Database } from './database'; import initializeServer from './server_Initialization'; // import GooglePhotosManager from './ApiManagers/GooglePhotosManager'; @@ -72,6 +73,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), + new AssistantManager(), ]; // initialize API Managers -- cgit v1.2.3-70-g09d2 From 5ff0bef5d3c4825aa7210a26c98aae3b24f4a835 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Fri, 17 May 2024 13:18:40 -0400 Subject: chatcards, quizcards, and ai flashcards --- src/client/apis/gpt/GPT.ts | 36 ++++-- src/client/util/CurrentUserUtils.ts | 2 - .../views/collections/CollectionCarouselView.tsx | 79 ++++++++++-- src/client/views/collections/CollectionView.tsx | 15 ++- src/client/views/nodes/ComparisonBox.scss | 143 +++++++++++++++++++++ src/client/views/nodes/ComparisonBox.tsx | 109 +++++++++++----- src/client/views/nodes/DocumentView.tsx | 24 +++- .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- src/client/views/pdf/AnchorMenu.tsx | 27 ++-- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 45 ------- src/client/views/pdf/PDFViewer.tsx | 4 +- 11 files changed, 367 insertions(+), 122 deletions(-) (limited to 'src/client/util') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 2757fc830..6600ddab2 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,10 +1,13 @@ import { ClientOptions, OpenAI } from 'openai'; +import { ChatCompletionMessageParam } from 'openai/resources'; enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + CHATCARD = 'chatcard', FLASHCARD = 'flashcard', + QUIZ = 'quiz', } type GPTCallOpts = { @@ -15,10 +18,17 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with questions and answers: ' }, - completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' }, + completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: '' }, + chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' }, + quiz: { + model: 'gpt-4-turbo', + maxTokens: 1024, + temp: 0, + prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct', + }, }; /** @@ -28,7 +38,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { * @returns AI Output */ const gptAPICall = async (inputText: string, callType: GPTCallType) => { - if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD) inputText += '.'; + if (!inputText) return 'Please provide a response.'; + if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD || GPTCallType.QUIZ) inputText += '.'; const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration: ClientOptions = { @@ -36,13 +47,20 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { dangerouslyAllowBrowser: true, }; const openai = new OpenAI(configuration); - const response = await openai.completions.create({ + + let messages: ChatCompletionMessageParam[] = [ + { role: 'system', content: opts.prompt }, + { role: 'user', content: inputText }, + ]; + + const response = await openai.chat.completions.create({ model: opts.model, - max_tokens: opts.maxTokens, + messages: messages, temperature: opts.temp, - prompt: `${opts.prompt}${inputText}`, + max_tokens: opts.maxTokens, }); - return response.choices[0].text; + + return response.choices[0].message.content; } catch (err) { console.log(err); return 'Error connecting with API.'; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ab0315ba1..b6ba3f187 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -342,8 +342,6 @@ pie title Minerals in my tap water }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, - // {key: "Flashcard", creator: Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, - //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 7f5176123..d45b0822b 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -33,29 +33,53 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + /** + * Goes to the next flashcard in the stack and filters + * based on the the currently selected option. + */ advance = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { - // go to a new index that is starred, skip the ones that aren't + if (this.layoutDoc[`filterOp`] == 'star') { + // go to a flashcard that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + + // if the user has gone through all of the cards and gotten them all correct, show all cards and exit practice mode + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + // set all the cards to missed + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + + /** + * Goes to the previous flashcard in the stack and filters + * based on the the currently selected option. + */ goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; @@ -63,38 +87,58 @@ export class CollectionCarouselView extends CollectionSubView() { var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + if (this.layoutDoc[`filterOp`] == 'star') { // go to a new index that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } + this.layoutDoc._carousel_index = startInd; + + // See all flashcards when finish going through practice mode and set all of the flashcards back to + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + /* + * Stars the document when the star button is pressed. + */ star = (e: React.MouseEvent) => { e.stopPropagation(); - // stars the document when the button is pressed const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + if (!curDoc) return; if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; }; + /* + * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode. + */ missed = (e: React.MouseEvent, val: string) => { e.stopPropagation(); const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; curDoc.layout[`${this.fieldKey}_missed`] = val; - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; - this.advance; + this.advance(e); }; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string): any => { @@ -154,6 +198,7 @@ export class CollectionCarouselView extends CollectionSubView() { ); } @computed get buttons() { + if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return; return ( <>
@@ -165,10 +210,10 @@ export class CollectionCarouselView extends CollectionSubView() {
-
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> +
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
-
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> +
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
@@ -185,6 +230,18 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} + {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} +

+ Add flashcards! +

+ {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */}

Recently missed!

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 168176edf..2f0f2a773 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -80,6 +80,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' }); - revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' }); - revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' }); + revealItems.push({ description: 'All', event: () => (this.layoutDoc[`filterOp`] = 'all'), icon: 'layer-group' }); + revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`filterOp`] = 'star'), icon: 'star' }); + revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`filterOp`] = 'practice'), icon: 'check' }); + revealItems.push({ description: 'Quiz Cards', event: () => (this.layoutDoc[`filterOp`] = 'quiz'), icon: 'pencil' }); - //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); - !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + // only show the filter options if it is a collection of type Carousel view + if (this.Document?._type_collection === CollectionViewType.Carousel) { + !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + } const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 39c864b2b..093b9c004 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -1,4 +1,5 @@ .comparisonBox-interactive, +.quiz-card, .comparisonBox { border-radius: inherit; width: 100%; @@ -7,6 +8,40 @@ z-index: 0; pointer-events: none; display: flex; + p { + color: rgb(0, 0, 0); + -webkit-text-stroke-color: black; + -webkit-text-stroke-width: 0.2px; + } + + .input-box { + position: relative; + padding: 10px; + width: 100%; + height: 100%; + display: flex; + } + + .submit-button { + position: relative; + padding-bottom: 10px; + padding-left: 5px; + padding-right: 5px; + width: 100%; + height: 15%; + display: flex; + + button { + flex: 1; + position: relative; + } + } + textarea { + flex: 1; + padding: 10px; + position: relative; + resize: none; + } .clip-div { position: absolute; @@ -95,4 +130,112 @@ display: flex; } } + // .input-box { + // position: relative; + // padding: 10px; + // } + // input[type='text'] { + // flex: 1; + // position: relative; + // margin-right: 10px; + // width: 100px; + // } +} + +// .quiz-card { +// position: relative; + +// input[type='text'] { +// flex: 1; +// position: relative; +// margin-right: 10px; +// width: 100px; +// } +// } +.QuizCard { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .QuizCard-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .QuizCardBox { + /* existing code */ + + .DIYNodeBox-iframe { + height: 100%; + width: 100%; + border: none; + } + } + + .search-bar { + display: flex; + justify-content: left; + align-items: left; + width: 100%; + padding: 10px; + + input[type='text'] { + flex: 1; + margin-right: 10px; + } + + button { + padding: 5px 10px; + } + } + + .content { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + .diagramBox { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + svg { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + } + } + + .loading-circle { + position: relative; + width: 50px; + height: 50px; + border-radius: 50%; + border: 3px solid #ccc; + border-top-color: #333; + animation: spin 1s infinite linear; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + } } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 19fccce8a..9fd4d696a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; import { Doc, Opt, DocListCast } from '../../../fields/Doc'; import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; @@ -34,6 +34,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() makeObservable(this); } + @observable inputValue = ''; + @observable outputValue = ''; + @observable loading = false; + @observable errorMessage = ''; + @observable outputMessage = ''; + + @action handleInputChange = (e: React.ChangeEvent) => { + this.inputValue = e.target.value; + console.log(this.inputValue); + }; + @observable _animating = ''; @computed get clipWidth() { @@ -160,7 +171,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() e => { const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move); de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - //this.clearDoc(which); return addDocument(doc); }; de.canEmbed = true; @@ -181,15 +191,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); _closeRef = React.createRef(); + /** + * Flips a flashcard to the alternate side for the user to view. + */ flipFlashcard = () => { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; + + /** + * Changes the view option to hover for a flashcard. + */ hoverFlip = (side: string | undefined) => { if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; }; + /** + * Creates the button used to flip the flashcards. + */ @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( @@ -203,21 +222,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() this.flipFlashcard(); console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); - - //const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text; - // DocCast(this.dataDoc[this.fieldKey + '_1'])[DocData].text = 'hello'; - // const mes = gptAPICall(queryText, GPTCallType.COMPLETION).trim(); - // const res = await gptAPICall(queryText, GPTCallType.COMPLETION) - // console.log(res); - //.then(value => (DocCast(this.dataDoc[this.fieldKey + '_1']).text = value.trim())); - if (usepath !== 'alternate') { - this.askGPT(); - } } }) } style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', background: usepath === 'alternate' ? 'white' : 'black', color: usepath === 'alternate' ? 'black' : 'white', }}> @@ -227,15 +235,34 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); } + @action handleRenderGPTClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; + this.outputValue = ''; + if (this.inputValue) this.askGPT(); + }; + + @action handleRenderClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; + }; + + /** + * Calls the GPT model to create QuizCards. Evaluates how similar the user's response is to the alternate + * side of the flashcard. + */ askGPT = async (): Promise => { - const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text; + const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); + const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText; + try { - let res = await gptAPICall(StrCast(queryText), GPTCallType.COMPLETION); + let res = await gptAPICall(queryText, GPTCallType.QUIZ); if (!res) { console.error('GPT call failed'); return; } - DocCast(this.dataDoc[this.fieldKey + '_0'])[DocData].text = res; + this.outputValue = res; console.log(res); } catch (err) { console.error('GPT call failed'); @@ -292,8 +319,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; const displayBox = (which: string, index: number, cover: number) => { - // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100; - // else this.layoutDoc.height = 300; return (
this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} @@ -326,36 +351,54 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - // add text box when first created + // add text box to each side when comparison box is first created if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[1]); + // if there is text from the pdf ai cards, put the question on the front side. newDoc[DocData].text = dataSplit[1]; this.addDoc(newDoc, this.fieldKey + '_0'); } if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[0]); - newDoc[DocData].text = 'placeholder...'; + // if there is text from the pdf ai cards, put the answer on the alternate side. + newDoc[DocData].text = dataSplit[0]; this.addDoc(newDoc, this.fieldKey + '_1'); } - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') { - { - return ( -
- {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)} - {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)} + // render the QuizCards + if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') { + return ( +
+

{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}

+ {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */} +
+ { +