diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.scss | 39 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 79 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 7 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 1 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 151 |
6 files changed, 211 insertions, 68 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 5dc818db8..585398204 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -43,7 +43,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { model: 'gpt-4o', maxTokens: 2048, temp: 0.5, - prompt: "I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok.", + prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:" }, describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' }, 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.' }, diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 8a721a626..5f7d1b901 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -26,23 +26,41 @@ display: flex; padding: 3px; // width: 300px; + // height:100px; + pointer-events: none; /* This ensures the container does not capture hover events */ + background-color: rgb(218, 218, 218); /* Background color of the container */ border-radius: 50px; /* Rounds the corners of the container */ transform: translateY(25px); // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ align-items: center; /* Centers buttons vertically */ justify-content: start; /* Centers buttons horizontally */ + + button { + pointer-events: auto; /* Re-enable pointer events for the buttons */ + + width: 70px; + height: 70px; + border-radius: 50%; + background-color: $dark-gray; + // border-color: $medium-blue; + margin: 5px; // transform: translateY(-50px); + background-color: transparent; + } } -button { - width: 35px; - height: 35px; - border-radius: 50%; - background-color: $dark-gray; - // border-color: $medium-blue; - margin: 5px; // transform: translateY(-50px); +.no-card-span{ + position: relative; + width: fit-content; + text-align: center; + font-size: 65px; + + + } + + // button:hover { // transform: translateY(-50px); // } @@ -74,6 +92,13 @@ button { flex-direction: column; } +// .card-item:hover { +// box-shadow: 0 20px 20px $medium-blue; +// transform: scale(1.05); + + +// } + .card-item-inactive { opacity: 0.5; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index d68fb57f8..7ea345e10 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -20,6 +20,13 @@ import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView } from './CollectionSubView'; import { FieldsDropdown } from '../FieldsDropdown'; +import { Button, IconButton } from 'browndash-components'; +import { faStar } from '@fortawesome/free-solid-svg-icons'; +import { FaStar, FaHeart, FaRobot, FaCloud } from 'react-icons/fa'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SettingsManager } from '../../util/SettingsManager'; +import { Tooltip } from '@mui/material'; + enum cardSortings { Time = 'time', @@ -72,8 +79,24 @@ export class CollectionCardView extends CollectionSubView() { constructor(props: any) { super(props); makeObservable(this); + this.setRegenerateCallback(); + + } + + setRegenerateCallback() { + GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc); } + @action + childPairStringListAndUpdateSortDesc = async () => { + const sortDesc = await this.childPairStringList(); // Await the promise to get the string result + GPTPopup.Instance.setSortDesc(sortDesc.join()); + GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult); + + }; + + + componentDidMount(): void { this._disposers.sort = reaction( () => ({ cardSort: this.cardSort, field: this.cardSort_customField }), @@ -331,7 +354,8 @@ export class CollectionCardView extends CollectionSubView() { */ calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { if (isSelected || amCards == 1) return 50 * this.fitContentScale; - const trans = isHovered ? this.translateHover(realIndex) : 0; + // const trans = isHovered ? this.translateHover(realIndex) : 0; + const trans = 0 return trans + this.translateY(amCards, calcRowIndex, realIndex); }; @@ -433,26 +457,50 @@ export class CollectionCardView extends CollectionSubView() { if (cardSort !== cardSortings.Custom) return ''; const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => this.cardSort_customField && set.add(NumCast(d[this.cardSort_customField])), new Set<number>()).size ?? 0); const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); - const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; + const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6; return ( - <div className="card-button-container" style={{ width: `${totalWidth}px` }}> + <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }} > {numberRange(amButtons).map(i => ( // eslint-disable-next-line jsx-a11y/control-has-associated-label - <button - key={i} - type="button" - style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} // - onClick={() => this.toggleButton(i, doc)} - /> + <Tooltip title={<div key = {i} className="dash-tooltip">Click to add/ remove this card from group {i +1}</div>}> + + <button key={i} type="button" + // style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} + onClick={() => this.toggleButton(i, doc)}> + {this.getButtonIcons(activeButtonIndex === i)} + </button> + + </Tooltip> + ))} </div> ); }; + + getButtonIcons = (isActive: boolean) => { + switch (this.cardSort_customField) { + case 'like': + return <FontAwesomeIcon icon = 'heart' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>; + case 'chat': + return <FontAwesomeIcon icon = 'robot' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>; + case 'idea': + return <FontAwesomeIcon icon = 'cloud' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>; + default: + return <FontAwesomeIcon icon = 'star' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>; + } + }; /** * Actually renders all the cards */ renderCards = () => { const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc)); + const isEmpty = this.childDocsWithoutLinks.length === 0; + + if (isEmpty) { + return <span className = 'no-card-span' style= {{width: `100%`}}> + Sorry ! There are no cards in this group</span> + } + // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { const realIndex = this.sortedDocs.filter(sortDoc => !DocumentView.SelectedDocs().includes(sortDoc)).indexOf(doc); @@ -486,23 +534,27 @@ export class CollectionCardView extends CollectionSubView() { }} style={{ width: this.panelWidth(), - height: 'max-content', // this.panelHeight(childPair.layout)(), + height: 'max-content', transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px) translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px) rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${isSelected ? 1.25 : 1})`, - }} + scale(${isSelected ? 1.25 : this._hoveredNodeIndex === index ? 1.05 : 1})`, + }} onMouseEnter={() => this.setHoveredNodeIndex(index)}> {this.displayDoc(doc, childScreenToLocal)} {this.renderButtons(doc, this.cardSort)} </div> ); - }); + } + + + ); }; render() { + return ( <div className="collectionCardView-outer" @@ -533,4 +585,5 @@ export class CollectionCardView extends CollectionSubView() { </div> ); } + } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 549cb5c8f..cef913837 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -352,24 +352,23 @@ ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) { switch(value){ case 'Custom 1': doc.cardSort_customField = "like"; - doc.cardSort_visibleSortGroups = new List<number>(); break; case 'Custom 2': doc.cardSort_customField = "star"; - doc.cardSort_visibleSortGroups = new List<number>(); break; case 'Custom 3': doc.cardSort_customField = "idea"; - doc.cardSort_visibleSortGroups = new List<number>(); break; case 'Chat GPT': doc.cardSort = "custom"; doc.cardSort_customField = "chat"; - doc.cardSort_visibleSortGroups = new List<number>(); break; default: break; } + + doc.cardSort_visibleSortGroups = new List<number>(); + } // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 6d8793f82..042b1dea5 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -84,6 +84,7 @@ $highlightedText: #82e0ff; font-size: 9px; padding: 10px; color: #ffffff; + width: 100%; background-color: $button; border-radius: 5px; } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index c0f17ba4e..468922532 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -20,6 +20,7 @@ import './GPTPopup.scss'; import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; + export enum GPTPopupMode { SUMMARY, EDIT, @@ -149,6 +150,28 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { this.cardsDoneLoading = done; } + @observable sortRespText: string = '' + + @action setSortRespText(resp: string) { + this.sortRespText = resp + } + + + @observable chatSortPrompt: string = "" + + sortPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => { + this.chatSortPrompt = e.target.value; + }); + + @observable private regenerateCallback: (() => Promise<void>) | null = null; + + @action public setRegenerateCallback(callback: () => Promise<void>) { + this.regenerateCallback = callback; + } + + + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; public createFilteredDoc: (axes?: any) => boolean = () => false; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; @@ -160,21 +183,34 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { this.setLoading(true); this.setSortDone(false); + if (this.regenerateCallback) { + await this.regenerateCallback(); + } + try { - const res = await gptAPICall(this.sortDesc, GPTCallType.SORT); + const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); // Trigger the callback with the result if (this.onSortComplete) { this.onSortComplete(res || 'Something went wrong :('); + + // Extract explanation surrounded by ------ at the top or both at the top and bottom + const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || []; + const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; + + // Set the extracted explanation to sortRespText + this.setSortRespText(explanation); + console.log(res); } } catch (err) { console.error(err); } - + this.setLoading(false); this.setSortDone(true); }; - + + /** * Generates a Dalle image and uploads it to the server. */ @@ -315,49 +351,75 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { }; sortBox = () => ( - <> - <div> + <> + <div> {this.heading('SORTING')} - <> - {!this.cardsDoneLoading || this.loading ? ( - <div className="content-wrapper"> - <div className="loading-spinner"> - <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> - {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>} - </div> + <> + {!this.cardsDoneLoading || this.loading ? ( + <div className="content-wrapper"> + <div className="loading-spinner"> + <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> + {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>} + </div> + </div> + ) : ( + !this.sortDone && ( + <> + <div className="btns-wrapper-gpt"> + <input + className="searchBox-input" + defaultValue="" + autoComplete="off" + onChange={this.sortPromptChanged} + onKeyDown={e => { + if (e.key === 'Enter') { + this.generateSort(); + } + e.stopPropagation(); + }} + type="text" + placeholder="How do you want to sort your cards ?" + id="search-input" + style={{ width: '100%' }} + /> </div> - ) : ( - !this.sortDone && ( - <div className="btns-wrapper-gpt"> - <Button - tooltip="Have ChatGPT sort your cards for you!" - text="Sort!" - onClick={this.generateSort} - color={StrCast(Doc.UserDoc().userVariantColor)} - type={Type.TERT} - style={{ - width: '90%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - }} - /> - </div> - ) - )} - - {this.sortDone && ( - <div> - <div className="content-wrapper"> - <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : 'Sorting done! Feel free to move things around / regenerate :) !'}</p> - <IconButton tooltip="Generate Again" onClick={() => this.setSortDone(false)} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> - </div> + <div className="btns-wrapper-gpt"> + <Button + tooltip="Have ChatGPT sort your cards for you!" + text="Sort!" + onClick={this.generateSort} + color={StrCast(Doc.UserDoc().userVariantColor)} + type={Type.TERT} + style={{ + width: '90%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + }} + /> </div> - )} - </> - </div> - </> - ); + </> + ) + )} + + {this.sortDone && ( + <div> + <div className="content-wrapper"> + <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p> + <IconButton + tooltip="Generate Again" + onClick={() => this.setSortDone(false)} + icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} + color={StrCast(Doc.UserDoc().userVariantColor)} + /> + </div> + </div> + )} + </> + </div> + </> +); + imageBox = () => ( <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> {this.heading('GENERATED IMAGE')} @@ -509,9 +571,12 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { render() { return ( + <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}> {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null} </div> + + ); } } |