diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 8 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 98 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.scss | 30 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 13 | ||||
-rw-r--r-- | src/extensions/ArrayExtensions.ts | 37 | ||||
-rw-r--r-- | src/extensions/StringExtensions.ts | 17 | ||||
-rw-r--r-- | src/fields/.PresField.ts.icloud | bin | 159 -> 0 bytes | |||
-rw-r--r-- | src/fields/PresField.ts | 6 |
9 files changed, 188 insertions, 22 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 1bec2fb11..3550b6216 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -97,6 +97,7 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: a max_tokens: opts.maxTokens, }); lastResp = response.choices[0].message.content ?? ''; + console.log('RESP:' + lastResp); return lastResp; } catch (err) { console.log(err); diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 4690e255f..4d199b360 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -157,11 +157,12 @@ .check-icon { position: absolute; - right: 150; + right: 40; bottom: 10; color: green; display: inline-block; - font-size: 100px; + font-size: 20px; + overflow: hidden; } .redo-icon { @@ -170,7 +171,8 @@ bottom: 10; color: black; display: inline-block; - font-size: 100px; + font-size: 20px; + overflow: hidden; } @keyframes spin { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 37827a43a..32b9e20a4 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -36,7 +36,7 @@ import { DocCast, NumCast, RTFCast, StrCast, ImageCast, Cast, toList } from '../ import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; import { URLField } from '../../../fields/URLField'; -import { gptImageLabel } from '../../apis/gpt/GPT'; +import { gptAPICall, GPTCallType, gptImageLabel } from '../../apis/gpt/GPT'; import ReactLoading from 'react-loading'; import { FollowLinkScript } from '../../documents/DocUtils'; import { basename } from 'path'; @@ -48,6 +48,12 @@ import axios from 'axios'; import { TupleType } from 'typescript'; // import stringSimilarity from 'string-similarity'; +enum quizMode { + SMART = 'smart', + NORMAL = 'normal', + NONE = 'none', +} + export class ImageEditorData { // eslint-disable-next-line no-use-before-define private static _instance: ImageEditorData; @@ -92,7 +98,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable private _width: number = 0; @observable private _height: number = 0; @observable private searchInput = ''; - @observable private _quizMode = false; + @observable private _quizMode = quizMode.NONE; @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @observable _curSuffix = ''; @observable _error = ''; @@ -359,11 +365,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - pushInfo = async () => { - this._quizMode = true; + pushInfo = async (quiz: quizMode) => { + this._quizMode = quiz; + this._loading = true; + console.log('JHSDKFJHKSDJFHKSJDHFKJSDHFKJHSDKF'); const img = { file: this.paths[0], + smart: quiz, }; const response = await axios.post('http://localhost:105/labels/', img, { headers: { @@ -399,8 +408,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { newCol.zIndex = 1000; newCol.forceActive = true; newCol.quiz = text; + newCol.showQuiz = false; this._quizBoxes.push(newCol); this.addDocument(newCol); + this._loading = false; } }; // static imageUrlToBase64 = async (imageUrl: string): Promise<string> => { @@ -496,25 +507,61 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return distance <= threshold; }; + extractHexAndSentences = (inputString: string) => { + // Regular expression to match a hexadecimal number at the beginning followed by a period and sentences + const regex = /^#([0-9A-Fa-f]+)\.\s*(.+)$/s; + const match = inputString.match(regex); + + if (match) { + const hexNumber = match[1]; + const sentences = match[2].trim(); + return { hexNumber, sentences }; + } else { + return { error: 'The input string does not match the expected format.' }; + } + }; + check = () => { - this._quizBoxes.forEach(doc => { + this._loading = true; + this._quizBoxes.forEach(async doc => { const input = StrCast(RTFCast(DocCast(doc).text)?.Text); console.log('INP: ' + StrCast(input) + '; DOC: ' + StrCast(doc.quiz)); - const match = this.compareWords(input, StrCast(doc.quiz)); - doc.backgroundColor = match ? '#11c249' : '#eb2d2d'; + if (this._quizMode == quizMode.SMART && input) { + const questionText = 'Question: What was labeled in this image?'; + const rubricText = ' Rubric: ' + StrCast(doc.quiz); + // const queryText = 'RealAnswer: ' + StrCast(doc.quiz) + '. UserAnswer: ' + input + '.'; + const queryText = + questionText + + ' UserAnswer: ' + + input + + '. ' + + rubricText + + '. One sentence and evaluate based on meaning, not wording. Provide a hex color at the beginning with a period after it on a scale of green (minor details missed) to red (big error) for how correct the answer is. Example: "#FFFFFF. Pasta is delicious."'; + const response = await gptAPICall(queryText, GPTCallType.QUIZ); + const hexSent = this.extractHexAndSentences(response); + console.log(hexSent.hexNumber); + doc.quiz = hexSent.sentences; + doc.backgroundColor = '#' + hexSent.hexNumber; + } else { + const match = this.compareWords(input, StrCast(doc.quiz)); + doc.backgroundColor = match ? '#11c249' : '#eb2d2d'; + } + doc.showQuiz = true; // console.log(this.compareWords(input, StrCast(doc.quiz)) ? 'Match' : 'No Match'); }); + this._loading = false; }; redo = () => { this._quizBoxes.forEach(doc => { DocCast(doc)[DocData].text = ''; doc.backgroundColor = '#e4e4e4'; + doc.showQuiz = false; }); }; exitQuizMode = () => { - this._quizMode = false; + this._quizMode = quizMode.NONE; this._quizBoxes.forEach(doc => { // this._props.removeDocument?.(DocCast(doc)); // this._props.DocumentView?.()._props.removeDocument?.(doc); @@ -531,16 +578,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; + const quizes: ContextMenuProps[] = []; // funcs.push({ description: 'Create ai flashcards', event: () => this.getImageDesc(), icon: 'id-card' }); - funcs.push({ - description: 'Quiz Mode', - event: !this._quizMode - ? this.pushInfo - : () => { - this._quizMode = false; - }, - icon: 'redo-alt', + quizes.push({ + description: 'Smart Check', + event: this._quizMode == quizMode.NONE ? () => this.pushInfo(quizMode.SMART) : this.exitQuizMode, + icon: 'pen-to-square', + }); + quizes.push({ + description: 'Normal', + event: this._quizMode == quizMode.NONE ? () => this.pushInfo(quizMode.NORMAL) : this.exitQuizMode, + icon: 'pencil', }); + // funcs.push({ description: 'Quiz Mode', subitems: optionItems, icon: 'eye' }); + // funcs.push({ + // description: 'Quiz Mode', + // event: !this._quizMode + // ? () => this.pushInfo(false) + // : () => { + // this._quizMode = false; + // }, + // icon: 'redo-alt', + // }); // funcs.push({ description: 'Get Text', event: this.check, icon: 'redo-alt' }); // funcs.push({ description: 'Get Labels2', event: this.getImageLabels2, icon: 'redo-alt' }); // funcs.push({ description: 'Get Labels', event: this.getImageLabels, icon: 'redo-alt' }); @@ -558,6 +617,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }), icon: 'pencil-alt', }); + ContextMenu.Instance?.addItem({ description: 'Quiz Mode', subitems: quizes, icon: 'file-pen' }); ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); } }; @@ -689,8 +749,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { )} </div> {this.overlayImageIcon} - {this._quizMode ? this.checkIcon : null} - {this._quizMode ? this.redoIcon : null} </div> ); } @@ -762,7 +820,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { height: this._props.PanelWidth() ? undefined : `100%`, pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, borderRadius, - overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : undefined, + overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden', }}> <CollectionFreeFormView ref={this._ffref} @@ -816,6 +874,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // anchorMenuFlashcard={() => this.getImageDesc()} /> )} + {this._quizMode != quizMode.NONE ? this.checkIcon : null} + {this._quizMode != quizMode.NONE ? this.redoIcon : null} </div> ); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 54643b4a5..227cd4312 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -79,7 +79,37 @@ audiotag:hover { right: 8px; position: absolute; } + .answer-icon { + position: absolute; + right: 10; + bottom: 10; + color: black; + display: inline-block; + font-size: 20px; + cursor: pointer; + border-radius: 50%; + } + + .q-icon { + position: absolute; + right: 6; + bottom: 10; + color: white; + display: inline-block; + font-size: 20px; + cursor: pointer; + border-radius: 50%; + } +} + +.answer-tooltip { + font-size: 15px; + padding: 2px; + max-width: 150; + line-height: 150%; + position: relative; } + .formattedTextBox-alternateButton { align-items: center; flex-direction: column; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 274330d31..b00437cf2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2092,6 +2092,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB </Tooltip> ); } + + @computed get answerIcon() { + return ( + <Tooltip title={<div className="answer-tooltip">{StrCast(this.Document.quiz)}</div>}> + <div className="answer-tool-tip"> + <FontAwesomeIcon className="q-icon" icon="circle" color="white" /> + <FontAwesomeIcon className="answer-icon" icon="question" /> + </div> + </Tooltip> + ); + } + get fieldKey() { return this._fieldKey; } @@ -2210,6 +2222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden || this.Document.quiz ? null : this.sidebarHandle} {this.audioHandle} {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null} + {this.Document.showQuiz ? this.answerIcon : null} </div> </div> ); diff --git a/src/extensions/ArrayExtensions.ts b/src/extensions/ArrayExtensions.ts new file mode 100644 index 000000000..8e125766d --- /dev/null +++ b/src/extensions/ArrayExtensions.ts @@ -0,0 +1,37 @@ +export default class ArrayExtension { + private readonly property: string; + private readonly body: <T>(this: Array<T>) => any; + + constructor(property: string, body: <T>(this: Array<T>) => any) { + this.property = property; + this.body = body; + } + + assign() { + Object.defineProperty(Array.prototype, this.property, { + value: this.body, + enumerable: false + }); + } + +} + +/** + * IMPORTANT: Any extension you add here *must* have a corresponding type definition + * in the Array<T> interface in ./General/ExtensionsTypings.ts. Otherwise, + * Typescript will not recognize your new function. + */ +const extensions = [ + new ArrayExtension("lastElement", function () { + if (!this.length) { + return undefined; + } + return this[this.length - 1]; + }) +]; + +function Assign() { + extensions.forEach(extension => extension.assign()); +} + +export { Assign };
\ No newline at end of file diff --git a/src/extensions/StringExtensions.ts b/src/extensions/StringExtensions.ts new file mode 100644 index 000000000..2c76e56c8 --- /dev/null +++ b/src/extensions/StringExtensions.ts @@ -0,0 +1,17 @@ +function Assign() { + + String.prototype.removeTrailingNewlines = function () { + let sliced = this; + while (sliced.endsWith("\n")) { + sliced = sliced.substring(0, this.length - 1); + } + return sliced as string; + }; + + String.prototype.hasNewline = function () { + return this.endsWith("\n"); + }; + +} + +export { Assign };
\ No newline at end of file diff --git a/src/fields/.PresField.ts.icloud b/src/fields/.PresField.ts.icloud Binary files differdeleted file mode 100644 index e63a55cc1..000000000 --- a/src/fields/.PresField.ts.icloud +++ /dev/null diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts new file mode 100644 index 000000000..f236a04fd --- /dev/null +++ b/src/fields/PresField.ts @@ -0,0 +1,6 @@ +//insert code here +import { ObjectField } from "./ObjectField"; + +export abstract class PresField extends ObjectField { + +}
\ No newline at end of file |