summaryrefslogtreecommitdiff
path: root/moneyjsx/src/settings.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'moneyjsx/src/settings.tsx')
-rw-r--r--moneyjsx/src/settings.tsx402
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>&nbsp;
+ <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 );
+}