From 1780d74c0b41a07133b1b148b393da6f714de895 Mon Sep 17 00:00:00 2001 From: amadeo Date: Fri, 4 Apr 2025 10:31:08 -0400 Subject: [PATCH 1/5] added spin status to webterm --- desk/sys.kelvin | 5 +- ui/App.tsx | 2 + ui/App.tsx.bak | 122 +++++++++++++++++++++++++++++++++++++++ ui/SpinInfoBar.tsx | 80 ++++++++++++++++++++++++++ ui/index.html | 6 +- ui/index.html.bak | 139 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 ui/App.tsx.bak create mode 100644 ui/SpinInfoBar.tsx create mode 100644 ui/index.html.bak diff --git a/desk/sys.kelvin b/desk/sys.kelvin index 0eb2698..1ec239e 100644 --- a/desk/sys.kelvin +++ b/desk/sys.kelvin @@ -1,4 +1 @@ -[%zuse 416] -[%zuse 415] -[%zuse 414] -[%zuse 413] +[%zuse 410] diff --git a/ui/App.tsx b/ui/App.tsx index 1c7937d..9ff5801 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -16,6 +16,7 @@ import Buffer from './Buffer'; import { DEFAULT_SESSION, SESSION_ID_REGEX } from './constants'; import { showSlog } from './lib/blit'; import { InfoButton } from './InfoButton'; +import { SpinInfoBar } from './SpinInfoBar'; import { scrySessions, pokeTask } from './lib/utils'; const initSessions = async () => { @@ -107,6 +108,7 @@ export default function TermApp() { return ( <> +
diff --git a/ui/App.tsx.bak b/ui/App.tsx.bak new file mode 100644 index 0000000..1c7937d --- /dev/null +++ b/ui/App.tsx.bak @@ -0,0 +1,122 @@ +import React, { + useCallback, useEffect +} from 'react'; + +import useTermState from './state'; +import { useDark } from './lib/useDark'; +import api from './api'; + +import { _dark, _light } from '@tlon/indigo-react'; + +import 'xterm/css/xterm.css'; + +import { ThemeProvider } from 'styled-components'; +import { Tabs } from './Tabs'; +import Buffer from './Buffer'; +import { DEFAULT_SESSION, SESSION_ID_REGEX } from './constants'; +import { showSlog } from './lib/blit'; +import { InfoButton } from './InfoButton'; +import { scrySessions, pokeTask } from './lib/utils'; + +const initSessions = async () => { + const response = await api.scry(scrySessions()); + + useTermState.getState().set((state) => { + state.names = response.sort(); + }); + + // if there is a query parameters called 'into', + // select that session, creating it if necessary + // + let match = RegExp('[?&]into=([^&]*)').exec(window.location.search); + let agent = match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + if ( agent && SESSION_ID_REGEX.test(agent) ) { + let session: string = agent; + // the session already exists, so we can simply select it + // + if ( response.indexOf(agent) > -1 ) { + useTermState.getState().set((state) => { + state.selected = session; + }); + } + // the session does not yet exist, so we create it, + // and connect it to the agent with the same name + // + else { + try { + await api.poke(pokeTask(session, { open: { term: agent, apps: [] } })); + useTermState.getState().set((state) => { + state.names = [session, ...state.names].sort(); + state.selected = session; + state.sessions[session] = null; + }); + } catch (error) { + console.log('unable to create session:', error); + } + } + } +}; + +export default function TermApp() { + const { names, selected } = useTermState(); + const dark = useDark(); + + const setupSlog = useCallback(() => { + console.log('slog: setting up...'); + let available = false; + const slog = new EventSource('/~_~/slog', { withCredentials: true }); + + slog.onopen = () => { + console.log('slog: opened stream'); + available = true; + }; + + slog.onmessage = (e) => { + const session = useTermState.getState().sessions[DEFAULT_SESSION]; + if (!session) { + console.log('slog: default session mia!', 'msg:', e.data); + console.log(Object.keys(useTermState.getState().sessions), session); + return; + } + showSlog(session.term, e.data); + }; + + slog.onerror = (e) => { + console.error('slog: eventsource error:', e); + if (available) { + window.setTimeout(() => { + if (slog.readyState !== EventSource.CLOSED) { + return; + } + console.log('slog: reconnecting...'); + setupSlog(); + }, 10000); + } + }; + + useTermState.getState().set((state) => { + state.slogstream = slog; + }); + }, []); + + useEffect(() => { + initSessions(); + setupSlog(); + }, []); + + return ( + <> + +
+ + +
+
+ {names.map((name) => { + return ; + })} +
+
+ + ); +} diff --git a/ui/SpinInfoBar.tsx b/ui/SpinInfoBar.tsx new file mode 100644 index 0000000..590026e --- /dev/null +++ b/ui/SpinInfoBar.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState } from 'react'; +import { useDark } from './lib/useDark'; + +export const SpinInfoBar = () => { + const [spinStack, setSpinStack] = useState([]); + const dark = useDark(); + + useEffect(() => { + console.log('spin: setting up...'); + let available = false; + const spin = new EventSource('/~_~/spin', { withCredentials: true }); + + spin.onopen = () => { + console.log('spin: opened stream'); + available = true; + }; + + spin.onmessage = (e) => { + console.log('spin message:', e.data); + // The data is expected to be a path like /stack3/stack2/stack1/stack0 + // We need to parse it into an array of stack names + if (e.data && typeof e.data === 'string') { + // Remove leading slash if present and split by slash + const stackPath = e.data.startsWith('/') ? e.data.substring(1) : e.data; + const stackArray = stackPath.split('/').filter(Boolean); + setSpinStack(stackArray); + } + }; + + spin.onerror = (e) => { + console.error('spin: eventsource error:', e); + if (available) { + window.setTimeout(() => { + if (spin.readyState !== EventSource.CLOSED) { + return; + } + console.log('spin: reconnecting...'); + // We would call the setup function again here, but we don't want to + // create multiple event listeners in this effect + }, 10000); + } + }; + + return () => { + spin.close(); + }; + }, []); + + if (spinStack.length === 0) { + return null; + } + + return ( +
+ Spin Stack: + {spinStack.map((stack, index) => ( + + {index > 0 && >} + + {stack} + + + ))} +
+ ); +}; diff --git a/ui/index.html b/ui/index.html index 4086ccc..79bc1c9 100644 --- a/ui/index.html +++ b/ui/index.html @@ -29,10 +29,14 @@ } .buffer-container { - height: calc(100% - 40px); + height: calc(100% - 40px - 24px); /* Subtract header height and spin-info-bar height */ position: relative; } + .spin-info-bar { + width: 100%; + } + .terminal-container { position: absolute; top: 0; diff --git a/ui/index.html.bak b/ui/index.html.bak new file mode 100644 index 0000000..4086ccc --- /dev/null +++ b/ui/index.html.bak @@ -0,0 +1,139 @@ + + + + Terminal + + + + + + + + + + + + + + +
+ + From 114830e44be84618fce4714639cbabda75b8084e Mon Sep 17 00:00:00 2001 From: amadeo Date: Fri, 4 Apr 2025 10:31:49 -0400 Subject: [PATCH 2/5] removed backups --- ui/App.tsx.bak | 122 ---------------------------------------- ui/index.html.bak | 139 ---------------------------------------------- 2 files changed, 261 deletions(-) delete mode 100644 ui/App.tsx.bak delete mode 100644 ui/index.html.bak diff --git a/ui/App.tsx.bak b/ui/App.tsx.bak deleted file mode 100644 index 1c7937d..0000000 --- a/ui/App.tsx.bak +++ /dev/null @@ -1,122 +0,0 @@ -import React, { - useCallback, useEffect -} from 'react'; - -import useTermState from './state'; -import { useDark } from './lib/useDark'; -import api from './api'; - -import { _dark, _light } from '@tlon/indigo-react'; - -import 'xterm/css/xterm.css'; - -import { ThemeProvider } from 'styled-components'; -import { Tabs } from './Tabs'; -import Buffer from './Buffer'; -import { DEFAULT_SESSION, SESSION_ID_REGEX } from './constants'; -import { showSlog } from './lib/blit'; -import { InfoButton } from './InfoButton'; -import { scrySessions, pokeTask } from './lib/utils'; - -const initSessions = async () => { - const response = await api.scry(scrySessions()); - - useTermState.getState().set((state) => { - state.names = response.sort(); - }); - - // if there is a query parameters called 'into', - // select that session, creating it if necessary - // - let match = RegExp('[?&]into=([^&]*)').exec(window.location.search); - let agent = match && decodeURIComponent(match[1].replace(/\+/g, ' ')); - if ( agent && SESSION_ID_REGEX.test(agent) ) { - let session: string = agent; - // the session already exists, so we can simply select it - // - if ( response.indexOf(agent) > -1 ) { - useTermState.getState().set((state) => { - state.selected = session; - }); - } - // the session does not yet exist, so we create it, - // and connect it to the agent with the same name - // - else { - try { - await api.poke(pokeTask(session, { open: { term: agent, apps: [] } })); - useTermState.getState().set((state) => { - state.names = [session, ...state.names].sort(); - state.selected = session; - state.sessions[session] = null; - }); - } catch (error) { - console.log('unable to create session:', error); - } - } - } -}; - -export default function TermApp() { - const { names, selected } = useTermState(); - const dark = useDark(); - - const setupSlog = useCallback(() => { - console.log('slog: setting up...'); - let available = false; - const slog = new EventSource('/~_~/slog', { withCredentials: true }); - - slog.onopen = () => { - console.log('slog: opened stream'); - available = true; - }; - - slog.onmessage = (e) => { - const session = useTermState.getState().sessions[DEFAULT_SESSION]; - if (!session) { - console.log('slog: default session mia!', 'msg:', e.data); - console.log(Object.keys(useTermState.getState().sessions), session); - return; - } - showSlog(session.term, e.data); - }; - - slog.onerror = (e) => { - console.error('slog: eventsource error:', e); - if (available) { - window.setTimeout(() => { - if (slog.readyState !== EventSource.CLOSED) { - return; - } - console.log('slog: reconnecting...'); - setupSlog(); - }, 10000); - } - }; - - useTermState.getState().set((state) => { - state.slogstream = slog; - }); - }, []); - - useEffect(() => { - initSessions(); - setupSlog(); - }, []); - - return ( - <> - -
- - -
-
- {names.map((name) => { - return ; - })} -
-
- - ); -} diff --git a/ui/index.html.bak b/ui/index.html.bak deleted file mode 100644 index 4086ccc..0000000 --- a/ui/index.html.bak +++ /dev/null @@ -1,139 +0,0 @@ - - - - Terminal - - - - - - - - - - - - - - -
- - From a806ddd5894a3343469d45f709e05fd77356cf9b Mon Sep 17 00:00:00 2001 From: amadeo Date: Fri, 9 May 2025 11:13:17 -0400 Subject: [PATCH 3/5] updated kelvin to 409 --- desk/sys.kelvin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/sys.kelvin b/desk/sys.kelvin index 1ec239e..86747fc 100644 --- a/desk/sys.kelvin +++ b/desk/sys.kelvin @@ -1 +1 @@ -[%zuse 410] +[%zuse 409] From a197725c4be663361f4893389d3b523cac9b202c Mon Sep 17 00:00:00 2001 From: amadeo Date: Fri, 9 May 2025 11:16:19 -0400 Subject: [PATCH 4/5] cleaned up comments --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 79bc1c9..f9c129b 100644 --- a/ui/index.html +++ b/ui/index.html @@ -29,7 +29,7 @@ } .buffer-container { - height: calc(100% - 40px - 24px); /* Subtract header height and spin-info-bar height */ + height: calc(100% - 40px - 24px); position: relative; } From b223fd56127550bb5a716cdcc6f8fe4057b219f4 Mon Sep 17 00:00:00 2001 From: amadeo Date: Fri, 16 May 2025 10:30:50 -0400 Subject: [PATCH 5/5] added @Fang-'s suggestion: --- ui/SpinInfoBar.tsx | 15 ++------------- ui/index.html | 6 +++--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/ui/SpinInfoBar.tsx b/ui/SpinInfoBar.tsx index 590026e..0c3269a 100644 --- a/ui/SpinInfoBar.tsx +++ b/ui/SpinInfoBar.tsx @@ -16,29 +16,18 @@ export const SpinInfoBar = () => { }; spin.onmessage = (e) => { - console.log('spin message:', e.data); // The data is expected to be a path like /stack3/stack2/stack1/stack0 // We need to parse it into an array of stack names if (e.data && typeof e.data === 'string') { // Remove leading slash if present and split by slash - const stackPath = e.data.startsWith('/') ? e.data.substring(1) : e.data; - const stackArray = stackPath.split('/').filter(Boolean); + const stackPath = e.data.substring(1); + const stackArray = stackPath.split('/'); setSpinStack(stackArray); } }; spin.onerror = (e) => { console.error('spin: eventsource error:', e); - if (available) { - window.setTimeout(() => { - if (spin.readyState !== EventSource.CLOSED) { - return; - } - console.log('spin: reconnecting...'); - // We would call the setup function again here, but we don't want to - // create multiple event listeners in this effect - }, 10000); - } }; return () => { diff --git a/ui/index.html b/ui/index.html index f9c129b..0907ca7 100644 --- a/ui/index.html +++ b/ui/index.html @@ -23,18 +23,18 @@