diff options
Diffstat (limited to 'moneyjsx/src/settings.tsx')
| -rw-r--r-- | moneyjsx/src/settings.tsx | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/moneyjsx/src/settings.tsx b/moneyjsx/src/settings.tsx new file mode 100644 index 0000000..ba39126 --- /dev/null +++ b/moneyjsx/src/settings.tsx @@ -0,0 +1,402 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import * as api from "./api"; +import * as util from "./util"; +import * as user from "./user"; +import * as terminal from "./terminal"; +import { + addPopup, onPopupClosed, closePopup, + Dropdown, DropdownItem, + Spinner, + OkPopup, OkCancelPopup +} from "./components"; + +let old_font = ''; +const fonts = [ + 'Terminal', + 'Arial', + 'Monospace', + 'Sans-serif', + 'Serif', + 'Times' +]; + +function showDeleteTokenPopup( id: number ) { + const deleteToken = async() => { + try { + await user.deleteToken( id ); + $( `#token-${ id }` ).remove(); + } catch( e: any ) { + addPopup( $( <OkPopup> + Error deleting token: { e.message } + </OkPopup> ) ); + } + } + + addPopup( $( + <OkCancelPopup onclick={ deleteToken }> + Are you sure you want to erase this API token? + </OkCancelPopup> + ) ); +} + +function showDeleteAllTokensPopup() { + const deleteTokens = async() => { + try { + await user.deleteAllTokens(); + closePopup(); + } catch( e: any ) { + addPopup( $( <OkPopup> + Error deleting all tokens: { e.message } + </OkPopup> ) ); + } + } + + addPopup( $( + <OkCancelPopup onclick={ deleteTokens }> + Are you sure you want to erase all API tokens? + </OkCancelPopup> + ) ); +} + +function TokenEntry( props: any ) { + const token = props.token; + + return <div class="tokens-entry" id={ `token-${ token.id }` }> + <span>{ token.value }</span> + <button class="tokens-delete" onclick={ () => showDeleteTokenPopup( token.id ) }>🗑</button> + </div> +} + +async function createNewToken( e: Event ) { + const btn = $( e.target ); + + e.preventDefault(); + e.stopPropagation(); + + const spinner = $( <Spinner /> ); + btn.append( spinner ); + + try { + const token = await user.createToken(); + const tokens = await user.getTokens(); + // re-draw token popup + closePopup(); + addPopup( $( <TokensPopup tokens={ tokens } /> ) ); + + const copyTokenToClipboard = () => { navigator.clipboard.writeText( token ); } + addPopup( $( + <OkCancelPopup + style="max-width: 330px; width: auto; text-align: center; display: flex; justify-content: center;" + onclick={ copyTokenToClipboard } + > + Your api token has been created.<br /> + Make sure to save it as it will not be shown to you again.<br /> + <div style="background-color: #000; width: 100%; overflow: scroll; padding: 5px"> + { token } + </div> + + Press OK to copy the token to clipboard. + </OkCancelPopup> + ) ); + } catch( e: any ) { + addPopup( $( + <OkPopup> + Error creating token: { e.message } + </OkPopup> + ) ); + } + + spinner.remove(); +} + +function TokensPopup( props: any ) { + return <div id="tokens-popup"> + { props.tokens.length > 0 && + <div id="tokens-inner"> + { props.tokens.map( ( tok: any ) => { + return <TokenEntry token={tok} /> + } ) } + </div> + } + { + props.tokens.length == 0 && <div> + No api tokens have been created. + </div> + } + <div style="display: flex; margin-top: 10px;"> + <button class="settings-btn" style="width: 150px; margin-right: 5px" onclick={ createNewToken }>Create new token</button> + <button class="settings-btn" style="width: 150px" onclick={ showDeleteAllTokensPopup }>Delete all tokens</button> + </div> + </div> +} + +async function openTokensPopup() { + let spinner = $( <Spinner /> ); + $( "#tokens-btn" ).append( spinner ); + try { + const tokens = await user.getTokens(); + spinner.remove(); + + return addPopup( $( <TokensPopup tokens={ tokens } /> ) ); + } catch( e: any ) { + spinner.remove(); + return addPopup( $( <OkPopup>{ e.message }</OkPopup> ) ); + } +} + + +function showDeleteNotePopup( note_id: string ) { + const deleteNote = async() => { + try { + await user.deleteNote( note_id ); + $( `#note-${ note_id }` ).remove(); + } catch( e: any ) { + addPopup( $( + <OkPopup> + Error deleting note<br />{ e.message } + </OkPopup> + ) ); + } + } + + addPopup( $( + <OkCancelPopup onclick={ deleteNote }> + Are you sure you want to delete this note? + </OkCancelPopup> + ) ); +} + +function showDeleteAllNotesPopup() { + addPopup( $( + <OkCancelPopup onclick={ async() => { await user.deleteAllNotes(); closePopup(); } }> + Are you sure you want to delete all notes? + </OkCancelPopup> + ) ); +} + +function NotesPopup( props: any ) { + return <div id="notes-popup"> + <div id="notes-inner"> + { props.notes.map( ( n: any ) => { + return <div class="notes-entry" id={ `note-${ n.id }` }> + <span>{ n.content.split(' : ')[1] }</span> + <button class="notes-delete" onclick={ () => showDeleteNotePopup( n.id ) }>🗑</button> + </div> + } ) } + </div> + <div style="display: flex; margin-top: 10px;"> + <button class="settings-btn" style="width: 200px" onclick={ showDeleteAllNotesPopup }>Delete all notes</button> + </div> + </div> +} + +async function openNotesPopup() { + let spinner = $( <Spinner /> ); + $( "#notes-btn" ).append( spinner ); + try { + const notes = await user.getNotes(); + spinner.remove(); + if( notes.length == 0 ) + return addPopup( $( <OkPopup>No notes found</OkPopup> ) ); + + return addPopup( $( <NotesPopup notes={ notes } /> ) ); + } catch( e: any ) { + spinner.remove(); + return addPopup( $( <OkPopup>{ e.message }</OkPopup> ) ); + } +} + +function onSettingsClosed() { + if( old_font.length > 0 ) { + const style = document.documentElement.style; + style.setProperty( "--site-font", old_font ); + } +} + +function onFontChanged( e: Event ) { + const el = $( e.target ); + const font = el.text(); + if( !old_font.length ) + old_font = user.settings.site_prefs.font; + + user.settings.site_prefs.font = font; + const style = document.documentElement.style; + style.setProperty( "--site-font", font ); + + $( "#settings-font" ).text( font ); + closePopup(); +} + +function onModelChanged( e: Event ) { + const el = $( e.target ); + const model = el.text(); + + if( user.settings.plan.plan == 'free' ) { + for( let m of api.models ) { + if( m.name == model && !m.free ) { + const popup = $( <OkCancelPopup onclick={ () => JSX.navigateParams( "/upgrade", {} ) }> + This model is only available for paid users. + Please upgrade your plan to use it. + </OkCancelPopup> ); + + return addPopup( popup ); + } + } + } + + user.settings.site_prefs.model = model; + + $( "#settings-model" ).text( model ); + closePopup(); +} + +async function save() { + const newprefs = { + site_prefs: user.settings.site_prefs, + prompt_data: user.settings.prompt_data, + nickname: user.settings.nickname + }; + + const newprompt = $( "#system-prompt" ).val(); + newprefs.prompt_data.system = newprompt as string; + + const newname = $( "#uname-setting" ).val() as string; + if( newname.length > 1 ) + newprefs.nickname = newname; + + $( "#settings-spinner-wrapper" ).append( <Spinner /> ); + try { + await user.savePrefs( newprefs ); + } catch( e: any ) { + $( "#settings-spinner-wrapper" ).empty(); + $( "#settings-spinner-wrapper" ).append( <div class="settings-err">{ e.message }</div> ); + return; + } + + old_font = ''; + $( "#settings-spinner-wrapper" ).empty(); + $( "#username" ).text( user.settings.nickname ); + $( "#uname-setting" ).attr( "placeholder", user.settings.nickname ); + terminal.updateCapabilitiesDisplay(); +} + +function PlanDisplay() { + const el = $( <div style="margin-top: 10px; display: flex; justify-content: space-evenly" /> ); + if( user.settings.plan.plan == 'free' ) { + el.append( + <span> + Plan: <b>free</b> + <a onclick={ () => JSX.navigate( "/upgrade" ) }>[ upgrade ]</a> + </span> + ); + } else { + el.append( + <span> + Plan: <b>{ user.settings.plan.plan }</b> + </span> + ); + + let days_left = ''; + if( user.settings.plan.endTime == -1 ) + days_left = 'inf'; + else + days_left = Math.floor( ( user.settings.plan.endTime - Date.now() ) / 60000 / 60 / 24 ).toString(); + el.append( + <span> + Time left: { days_left.toString() } days + </span> + ); + } + + return el[0]; +} + +async function downloadAllData() { + const spinner = $( <Spinner /> ); + $( "#settings-spinner-wrapper" ).append( spinner ); + + try { + await user.downloadAllData(); + } catch( e: any ) { + addPopup( $( + <OkPopup> + Error processing request: { e.message } + </OkPopup> + ) ); + } + + spinner.remove(); +} + +async function invalidateAllSessions() { + const spinner = $( <Spinner /> ); + $( "#settings-spinner-wrapper" ).append( spinner ); + + try { + await user.invalidateAllSessions(); + } catch( e: any ) { + addPopup( $( + <OkPopup> + Error invalidating sessions: { e.message } + </OkPopup> + ) ); + } + + spinner.remove(); +} + +function SettingsPopup() { + return <div id="settings-popup"> + <div id="settings-top"> + <div> + <label>System prompt</label><br/> + <textarea maxlength="1024" placeholder="..." id="system-prompt"> + { util.escapeHtml( user.settings.prompt_data.system || '' ) } + </textarea> + </div> + <div> + <div class="chat-column"> + <label style="margin-bottom: 1px">Username</label> + <input type="text" placeholder={ user.settings.nickname || '' } id="uname-setting" /> + </div> + <div class="chat-column"> + <label>Site font</label> + <Dropdown title={ user.settings.site_prefs.font } id="settings-font" inline> + { fonts.map( ( f ) => <DropdownItem onclick={ onFontChanged }>{ f }</DropdownItem> ) } + </Dropdown> + </div> + <div class="chat-column"> + <label>Chat model</label> + <Dropdown title={ user.settings.site_prefs.model! } id="settings-model" inline> + { api.models.map( ( m ) => { + if( m.free || user.settings.plan.plan == 'paid' ) + return <DropdownItem onclick={ onModelChanged }>{ m.name }</DropdownItem>; + return <DropdownItem onclick={ onModelChanged } style="color: #888">{ m.name }</DropdownItem>; + } ) } + </Dropdown> + </div> + <div style="display: flex; justify-content: space-between; margin-top: 10px"> + <button class="settings-btn" id="notes-btn" onclick={ openNotesPopup }>View notes</button> + <button class="settings-btn" id="tokens-btn" onclick={ openTokensPopup }>View tokens</button> + </div> + <PlanDisplay /> + </div> + </div> + + <div style="display: flex; flex-direction: column;"> + <button style="margin: 2px" onclick={ downloadAllData }>download all my data</button> + <button style="margin: 2px" onclick={ invalidateAllSessions }>sign out all devices</button> + <button style="margin: 2px" onclick={ save }>Save</button> + <div id="settings-spinner-wrapper" /> + </div> + </div>; +} + +export function openPopup() { + if( !user.is_loggedin ) + return; + const el = $( <SettingsPopup /> ); + addPopup( el ); + onPopupClosed( onSettingsClosed ); +} |
