aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIEatChili <nanunguyen99@gmail.com>2024-06-12 12:32:19 -0400
committerIEatChili <nanunguyen99@gmail.com>2024-06-12 12:32:19 -0400
commit05e634842f7d798b8f8c201c7ff7821b32de376f (patch)
tree5bd91177eb17e9c50ea5698ce283742cfd657ecb /src
parent56568bbb0db34c0129b6a3c8770ce3f889dcbd31 (diff)
feat: updated and fixed ui
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.scss40
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx159
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx46
5 files changed, 164 insertions, 85 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 486b6815a..cb3d9df62 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -458,7 +458,7 @@ pie title Minerals in my tap water
{ title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}},
{ title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}},
{ title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
- { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), ignoreClick: true, icon: "folder-open", hidden: true }
+ { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), ignoreClick: true, icon: "folder-open", hidden: false }
].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(this)'}}));
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5b4c2b5ba..716edc22d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -76,7 +76,7 @@ import { PresBox } from './nodes/trails';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { TopBar } from './topbar/TopBar';
-import { ImageLabelBox } from './collections/collectionFreeForm/ImageLabelBox';
+import { ImageLabelBox, ImageLabelBoxData } from './collections/collectionFreeForm/ImageLabelBox';
const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
const _global = (window /* browser */ || global) /* node */ as any;
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.scss b/src/client/views/collections/collectionFreeForm/ImageLabelBox.scss
index 5f2ce4e14..819c72760 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.scss
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.scss
@@ -41,9 +41,45 @@
}
}
-.image-information {
+.image-information-list {
display: flex;
flex-direction: column;
- //align-items: center;
+ align-items: center;
+ width: 100%;
+ margin-top: 10px;
+}
+
+.image-information {
+ border: 1px solid;
width: 100%;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ padding: 2px;
+ overflow-x: auto;
+ overflow-y: auto;
+
+ img {
+ max-width: 200px;
+ max-height: 200px;
+ width: auto;
+ height: auto;
+ }
+}
+
+.image-information-labels {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ .image-label {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding: 3px;
+ border-radius: 2px;
+ border: solid 1px;
+ }
}
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index 83ae69cfb..f5530ccc4 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Colors, IconButton } from 'browndash-components';
-import { action, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import React from 'react';
import { Doc } from '../../../../fields/Doc';
@@ -11,12 +11,51 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './ImageLabelBox.scss';
import { MainView } from '../../MainView';
-import { MarqueeView } from './MarqueeView';
import 'ldrs/ring';
import { ring } from 'ldrs';
import { SnappingManager } from '../../../util/SnappingManager';
import { ImageCast } from '../../../../fields/Types';
import { DocData } from '../../../../fields/DocSymbols';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { CollectionCardView } from '../CollectionCardDeckView';
+import { gptGetEmbedding, gptImageLabel } from '../../../apis/gpt/GPT';
+import { numberRange, Utils } from '../../../../Utils';
+import { List } from '../../../../fields/List';
+
+export class ImageLabelBoxData {
+ static _instance: ImageLabelBoxData;
+ @observable _docs: Doc[] = [];
+ @observable _labelGroups: string[] = [];
+
+ constructor() {
+ makeObservable(this);
+ ImageLabelBoxData._instance = this;
+ }
+ public static get Instance() {
+ return ImageLabelBoxData._instance ?? new ImageLabelBoxData();
+ }
+
+ @action
+ public setData = (docs: Doc[]) => {
+ this._docs = docs;
+ };
+
+ @action
+ addLabel = (label: string) => {
+ label = label.toUpperCase().trim();
+ if (label.length > 0) {
+ if (!this._labelGroups.includes(label)) {
+ this._labelGroups = [...this._labelGroups, label];
+ }
+ }
+ };
+
+ @action
+ removeLabel = (label: string) => {
+ const labelUp = label.toUpperCase();
+ this._labelGroups = this._labelGroups.filter(group => group !== labelUp);
+ };
+}
@observer
export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -24,59 +63,53 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
return FieldView.LayoutString(ImageLabelBox, fieldKey);
}
- public static Instance: ImageLabelBox | null = null;
+ public static Instance: ImageLabelBox;
private _inputRef = React.createRef<HTMLInputElement>();
- @observable _loading: boolean = true;
- @observable _currentLabel: string = '';
- @observable _labelGroups: string[] = [];
- @observable _selectedImages: Doc[] = [];
+ @observable _loading: boolean = false;
+ private _currentLabel: string = '';
+
+ @computed get _labelGroups() {
+ return ImageLabelBoxData.Instance._labelGroups;
+ }
+
+ @computed get _selectedImages() {
+ // return DocListCast(this.dataDoc.data);
+ return ImageLabelBoxData.Instance._docs;
+ }
@observable _displayImageInformation: boolean = false;
constructor(props: any) {
super(props);
makeObservable(this);
- ImageLabelBox.Instance = this;
ring.register();
+ ImageLabelBox.Instance = this;
console.log('Image Box Has Been Initialized');
}
+ // ImageLabelBox.Instance.setData()
/**
* This method is called when the SearchBox component is first mounted. When the user opens
* the search panel, the search input box is automatically selected. This allows the user to
* type in the search input box immediately, without needing clicking on it first.
*/
componentDidMount() {
- // if (this._inputRef.current) {
- // this._inputRef.current.focus();
- // }
+ this.classifyImagesInBox();
+ reaction(
+ () => this._selectedImages,
+ () => this.classifyImagesInBox()
+ );
}
@action
- addLabel = (label: string) => {
- label = label.toUpperCase().trim();
- if (label.length > 0) {
- if (!this._labelGroups.includes(label)) {
- this._labelGroups = [...this._labelGroups, label];
- }
- }
- };
-
- @action
- removeLabel = (label: string) => {
- const labelUp = label.toUpperCase();
- this._labelGroups = this._labelGroups.filter(group => group !== labelUp);
- };
-
- @action
groupImages = () => {
MarqueeOptionsMenu.Instance.groupImages();
- this._labelGroups = [];
MainView.Instance.closeFlyout();
};
@action
startLoading = () => {
this._loading = true;
+ console.log('Start loading has been called!');
};
@action
@@ -85,8 +118,38 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@action
- setSelectedImages = (selected: Doc[]) => {
- this._selectedImages = selected;
+ toggleDisplayInformation = () => {
+ this._displayImageInformation = !this._displayImageInformation;
+ };
+
+ onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this._currentLabel = e.target.value;
+ });
+
+ classifyImagesInBox = async () => {
+ this.startLoading();
+
+ const imageInfos = this._selectedImages.map(async doc => {
+ if (!doc[DocData].data_labels) {
+ const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
+ return CollectionCardView.imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
+ !hrefBase64 ? undefined :
+ gptImageLabel(hrefBase64).then(labels =>
+ Promise.all(labels.split('\n').map(label => gptGetEmbedding(label))).then(embeddings =>
+ ({ doc, embeddings, labels }))) ); // prettier-ignore
+ }
+ }); // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them.
+
+ (await Promise.all(imageInfos)).forEach(imageInfo => {
+ if (imageInfo && imageInfo.embeddings && Array.isArray(imageInfo.embeddings)) {
+ imageInfo.doc[DocData].data_labels = imageInfo.labels;
+ numberRange(3).forEach(n => {
+ imageInfo.doc[`data_labels_embedding_${n + 1}`] = new List<number>(imageInfo.embeddings[n]);
+ });
+ }
+ }); // Add the labels as fields to each image.
+
+ this.endLoading();
};
render() {
@@ -98,14 +161,20 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
+ if (this._selectedImages.length === 0) {
+ return (
+ <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
+ <p style={{ fontSize: 'large' }}>In order to classify and sort images, marquee select the desired images and press the 'Classify and Sort Images' button. Then, add the desired groups for the images to be put in.</p>
+ </div>
+ );
+ }
+
return (
<div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
<div className="searchBox-bar" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
<IconButton
tooltip={'See image information'}
- onPointerDown={() => {
- this._displayImageInformation = !this._displayImageInformation;
- }}
+ onPointerDown={this.toggleDisplayInformation}
icon={this._displayImageInformation ? <FontAwesomeIcon icon="caret-up" /> : <FontAwesomeIcon icon="caret-down" />}
color={MarqueeOptionsMenu.Instance.userColor}
style={{ width: '19px' }}
@@ -113,6 +182,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
defaultValue=""
autoComplete="off"
+ onChange={this.onInputChange}
// onKeyDown={e => {
// e.key === 'Enter' ? this.submitSearch() : null;
// e.stopPropagation();
@@ -129,8 +199,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
tooltip={'Add a label'}
onPointerDown={() => {
const input = document.getElementById('new-label') as HTMLInputElement;
- const newLabel = input.value;
- this.addLabel(newLabel);
+ ImageLabelBoxData.Instance.addLabel(this._currentLabel);
this._currentLabel = '';
input.value = '';
}}
@@ -144,12 +213,12 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="image-label-list">
{this._labelGroups.map(group => {
return (
- <div>
+ <div key={Utils.GenerateGuid()}>
<p style={{ color: MarqueeOptionsMenu.Instance.userColor }}>{group}</p>
<IconButton
tooltip={'Remove Label'}
onPointerDown={() => {
- this.removeLabel(group);
+ ImageLabelBoxData.Instance.removeLabel(group);
}}
icon={'x'}
color={MarqueeOptionsMenu.Instance.userColor}
@@ -161,15 +230,21 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
{this._displayImageInformation ? (
- <div className="image-information">
+ <div className="image-information-list">
{this._selectedImages.map(doc => {
const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
return (
- <div>
+ <div className="image-information" style={{ borderColor: SettingsManager.userColor }} key={Utils.GenerateGuid()}>
<img src={`${name}_o.${type}`}></img>
- {(doc[DocData].data_labels as string).split('\n').map(label => {
- return <div className="image-label">{label}</div>;
- })}
+ <div className="image-information-labels">
+ {(doc[DocData].data_labels as string).split('\n').map(label => {
+ return (
+ <div key={Utils.GenerateGuid()} className="image-label" style={{ backgroundColor: SettingsManager.userVariantColor, borderColor: SettingsManager.userColor }}>
+ {label}
+ </div>
+ );
+ })}
+ </div>
</div>
);
})}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index f98883bff..81f2a94c1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -37,7 +37,7 @@ import { ImageLabelHandler } from './ImageLabelHandler';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
import { MainView } from '../../MainView';
-import { ImageLabelBox } from './ImageLabelBox';
+import { ImageLabelBox, ImageLabelBoxData } from './ImageLabelBox';
import { SearchBox } from '../../search/SearchBox';
interface MarqueeViewProps {
@@ -442,44 +442,12 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
*/
@undoBatch
classifyImages = action(async (e: React.MouseEvent | undefined) => {
- if (e) {
- const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
- if (groupButton) {
- MainView.Instance.expandFlyout(groupButton);
- while (!ImageLabelBox.Instance) {
- await new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
- console.log('Waiting for Image Label Box');
- });
- }
- ImageLabelBox.Instance.startLoading();
- }
+ const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
+ if (groupButton) {
+ this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG);
+ ImageLabelBoxData.Instance.setData(this._selectedDocs);
+ MainView.Instance.expandFlyout(groupButton);
}
-
- this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG); // Get the selected documents from the marquee select.
- ImageLabelBox.Instance!.setSelectedImages(this._selectedDocs);
-
- const imageInfos = this._selectedDocs.map(async doc => {
- if (!doc[DocData].data_labels) {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
- return CollectionCardView.imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
- !hrefBase64 ? undefined :
- gptImageLabel(hrefBase64).then(labels =>
- Promise.all(labels.split('\n').map(label => gptGetEmbedding(label))).then(embeddings =>
- ({ doc, embeddings, labels }))) ); // prettier-ignore
- }
- }); // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them.
-
- (await Promise.all(imageInfos)).forEach(imageInfo => {
- if (imageInfo && imageInfo.embeddings && Array.isArray(imageInfo.embeddings)) {
- imageInfo.doc[DocData].data_labels = imageInfo.labels;
- numberRange(3).forEach(n => {
- imageInfo.doc[`data_labels_embedding_${n + 1}`] = new List<number>(imageInfo.embeddings[n]);
- });
- }
- }); // Add the labels as fields to each image.
-
- ImageLabelBox.Instance!.endLoading();
- console.log(this._selectedDocs);
});
/**
@@ -496,7 +464,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
// most similar one.
this._selectedDocs.forEach(doc => {
const embedLists = numberRange(3).map(n => Array.from(NumListCast(doc[`data_labels_embedding_${n + 1}`])));
- const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map(l => (embedding && similarity(Array.from(embedding), l)) || 0));
+ const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map((l, index) => (embedding && (1 - index * 0.1) * similarity(Array.from(embedding), l)!) || 0));
const {label: mostSimilarLabelCollect} =
labelGroups.map(label => ({ label, similarityScore: bestEmbedScore(labelToEmbedding.get(label)) }))
.reduce((prev, cur) => cur.similarityScore < 0.3 || cur.similarityScore <= prev.similarityScore ? prev: cur,