@@ -27,6 +27,7 @@ import { useDialog } from "@tui/ui/dialog"
2727import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
2828import { DialogAlert } from "../../ui/dialog-alert"
2929import { useToast } from "../../ui/toast"
30+ import { useKV } from "../../context/kv"
3031
3132export type PromptProps = {
3233 sessionID ?: string
@@ -35,6 +36,7 @@ export type PromptProps = {
3536 ref ?: ( ref : PromptRef ) => void
3637 hint ?: JSX . Element
3738 showPlaceholder ?: boolean
39+ footerVisible ?: boolean
3840}
3941
4042export type PromptRef = {
@@ -115,7 +117,13 @@ export function Prompt(props: PromptProps) {
115117 const sync = useSync ( )
116118 const dialog = useDialog ( )
117119 const toast = useToast ( )
120+ const kv = useKV ( )
118121 const status = createMemo ( ( ) => sync . data . session_status [ props . sessionID ?? "" ] ?? { type : "idle" } )
122+ const permissions = createMemo ( ( ) => {
123+ if ( ! props . sessionID ) return [ ]
124+ return sync . data . permission [ props . sessionID ] ?? [ ]
125+ } )
126+ const shortcutsVisible = createMemo ( ( ) => ! kv . get ( "shortcuts_hidden" , false ) )
119127 const history = usePromptHistory ( )
120128 const command = useCommandDialog ( )
121129 const renderer = useRenderer ( )
@@ -844,17 +852,50 @@ export function Prompt(props: PromptProps) {
844852 cursorColor = { theme . text }
845853 syntaxStyle = { syntax ( ) }
846854 />
847- < box flexDirection = "row" flexShrink = { 0 } paddingTop = { 1 } gap = { 1 } >
848- < text fg = { highlight ( ) } >
849- { store . mode === "shell" ? "Shell" : Locale . titlecase ( local . agent . current ( ) . name ) } { " " }
850- </ text >
851- < Show when = { store . mode === "normal" } >
852- < box flexDirection = "row" gap = { 1 } >
853- < text flexShrink = { 0 } fg = { theme . text } >
854- { local . model . parsed ( ) . model }
855- </ text >
856- < text fg = { theme . textMuted } > { local . model . parsed ( ) . provider } </ text >
857- </ box >
855+ < box flexDirection = "row" flexShrink = { 0 } paddingTop = { 1 } gap = { 1 } justifyContent = "space-between" >
856+ < box flexDirection = "row" gap = { 1 } >
857+ < text fg = { highlight ( ) } >
858+ { store . mode === "shell" ? "Shell" : Locale . titlecase ( local . agent . current ( ) . name ) } { " " }
859+ </ text >
860+ < Show when = { store . mode === "normal" } >
861+ < box flexDirection = "row" gap = { 1 } >
862+ < text flexShrink = { 0 } fg = { theme . text } >
863+ { local . model . parsed ( ) . model }
864+ </ text >
865+ < text fg = { theme . textMuted } > { local . model . parsed ( ) . provider } </ text >
866+ </ box >
867+ </ Show >
868+ </ box >
869+ < Show when = { store . mode === "normal" && props . sessionID && ! props . footerVisible } >
870+ < Switch >
871+ < Match when = { permissions ( ) . length > 0 } >
872+ < text fg = { theme . warning } >
873+ < span style = { { fg : theme . warning } } > ◉</ span > { permissions ( ) . length } Permission
874+ { permissions ( ) . length > 1 ? "s" : "" } pending
875+ </ text >
876+ </ Match >
877+ < Match when = { status ( ) . type !== "idle" } >
878+ < box flexDirection = "row" gap = { 1 } flexShrink = { 0 } >
879+ < spinner color = { spinnerDef ( ) . color } frames = { spinnerDef ( ) . frames } interval = { 40 } />
880+ < text fg = { store . interrupt > 0 ? theme . primary : theme . text } >
881+ esc{ " " }
882+ < span style = { { fg : store . interrupt > 0 ? theme . primary : theme . textMuted } } >
883+ { store . interrupt > 0 ? "again to interrupt" : "interrupt" }
884+ </ span >
885+ </ text >
886+ </ box >
887+ </ Match >
888+ < Match when = { shortcutsVisible ( ) } >
889+ < box gap = { 2 } flexDirection = "row" flexShrink = { 0 } >
890+ < text fg = { theme . text } >
891+ { keybind . print ( "agent_cycle" ) } < span style = { { fg : theme . textMuted } } > switch agent</ span >
892+ </ text >
893+ < text fg = { theme . text } >
894+ { keybind . print ( "command_list" ) } < span style = { { fg : theme . textMuted } } > commands</ span >
895+ </ text >
896+ </ box >
897+ </ Match >
898+ </ Switch >
858899 </ Show >
859900 </ box >
860901 </ box >
@@ -886,103 +927,117 @@ export function Prompt(props: PromptProps) {
886927 }
887928 />
888929 </ box >
889- < box flexDirection = "row" justifyContent = "space-between" >
890- < Show when = { status ( ) . type !== "idle" } fallback = { < text /> } >
891- < box
892- flexDirection = "row"
893- gap = { 1 }
894- flexGrow = { 1 }
895- justifyContent = { status ( ) . type === "retry" ? "space-between" : "flex-start" }
896- >
897- < box flexShrink = { 0 } flexDirection = "row" gap = { 1 } >
898- { /* @ts -ignore // SpinnerOptions doesn't support marginLeft */ }
899- < spinner marginLeft = { 1 } color = { spinnerDef ( ) . color } frames = { spinnerDef ( ) . frames } interval = { 40 } />
900- < box flexDirection = "row" gap = { 1 } flexShrink = { 0 } >
901- { ( ( ) => {
902- const retry = createMemo ( ( ) => {
903- const s = status ( )
904- if ( s . type !== "retry" ) return
905- return s
906- } )
907- const message = createMemo ( ( ) => {
908- const r = retry ( )
909- if ( ! r ) return
910- if ( r . message . includes ( "exceeded your current quota" ) && r . message . includes ( "gemini" ) )
911- return "gemini is way too hot right now"
912- if ( r . message . length > 80 ) return r . message . slice ( 0 , 80 ) + "..."
913- return r . message
914- } )
915- const isTruncated = createMemo ( ( ) => {
916- const r = retry ( )
917- if ( ! r ) return false
918- return r . message . length > 120
919- } )
920- const [ seconds , setSeconds ] = createSignal ( 0 )
921- onMount ( ( ) => {
922- const timer = setInterval ( ( ) => {
923- const next = retry ( ) ?. next
924- if ( next ) setSeconds ( Math . round ( ( next - Date . now ( ) ) / 1000 ) )
925- } , 1000 )
926-
927- onCleanup ( ( ) => {
928- clearInterval ( timer )
930+ < Show when = { store . mode === "shell" } >
931+ < box gap = { 2 } flexDirection = "row" >
932+ < text fg = { theme . text } >
933+ esc < span style = { { fg : theme . textMuted } } > exit shell mode</ span >
934+ </ text >
935+ </ box >
936+ </ Show >
937+ < Show when = { store . mode !== "shell" } >
938+ < box flexDirection = "row" justifyContent = "space-between" >
939+ < Show when = { props . footerVisible && status ( ) . type !== "idle" } fallback = { < text /> } >
940+ < box
941+ flexDirection = "row"
942+ gap = { 1 }
943+ flexGrow = { 1 }
944+ justifyContent = { status ( ) . type === "retry" ? "space-between" : "flex-start" }
945+ >
946+ < box flexShrink = { 0 } flexDirection = "row" gap = { 1 } >
947+ { /* @ts -ignore // SpinnerOptions doesn't support marginLeft */ }
948+ < spinner marginLeft = { 1 } color = { spinnerDef ( ) . color } frames = { spinnerDef ( ) . frames } interval = { 40 } />
949+ < box flexDirection = "row" gap = { 1 } flexShrink = { 0 } >
950+ { ( ( ) => {
951+ const retry = createMemo ( ( ) => {
952+ const s = status ( )
953+ if ( s . type !== "retry" ) return
954+ return s
955+ } )
956+ const message = createMemo ( ( ) => {
957+ const r = retry ( )
958+ if ( ! r ) return
959+ if ( r . message . includes ( "exceeded your current quota" ) && r . message . includes ( "gemini" ) )
960+ return "gemini is way too hot right now"
961+ if ( r . message . length > 80 ) return r . message . slice ( 0 , 80 ) + "..."
962+ return r . message
929963 } )
930- } )
931- const handleMessageClick = ( ) => {
932- const r = retry ( )
933- if ( ! r ) return
934- if ( isTruncated ( ) ) {
935- DialogAlert . show ( dialog , "Retry Error" , r . message )
964+ const isTruncated = createMemo ( ( ) => {
965+ const r = retry ( )
966+ if ( ! r ) return false
967+ return r . message . length > 120
968+ } )
969+ const [ seconds , setSeconds ] = createSignal ( 0 )
970+ onMount ( ( ) => {
971+ const timer = setInterval ( ( ) => {
972+ const next = retry ( ) ?. next
973+ if ( next ) setSeconds ( Math . round ( ( next - Date . now ( ) ) / 1000 ) )
974+ } , 1000 )
975+
976+ onCleanup ( ( ) => {
977+ clearInterval ( timer )
978+ } )
979+ } )
980+ const handleMessageClick = ( ) => {
981+ const r = retry ( )
982+ if ( ! r ) return
983+ if ( isTruncated ( ) ) {
984+ DialogAlert . show ( dialog , "Retry Error" , r . message )
985+ }
936986 }
937- }
938987
939- const retryText = ( ) => {
940- const r = retry ( )
941- if ( ! r ) return ""
942- const baseMessage = message ( )
943- const truncatedHint = isTruncated ( ) ? " (click to expand)" : ""
944- const retryInfo = ` [retrying ${ seconds ( ) > 0 ? `in ${ seconds ( ) } s ` : "" } attempt #${ r . attempt } ]`
945- return baseMessage + truncatedHint + retryInfo
946- }
988+ const retryText = ( ) => {
989+ const r = retry ( )
990+ if ( ! r ) return ""
991+ const baseMessage = message ( )
992+ const truncatedHint = isTruncated ( ) ? " (click to expand)" : ""
993+ const retryInfo = ` [retrying ${ seconds ( ) > 0 ? `in ${ seconds ( ) } s ` : "" } attempt #${ r . attempt } ]`
994+ return baseMessage + truncatedHint + retryInfo
995+ }
947996
948- return (
949- < Show when = { retry ( ) } >
950- < box onMouseUp = { handleMessageClick } >
951- < text fg = { theme . error } > { retryText ( ) } </ text >
952- </ box >
953- </ Show >
954- )
955- } ) ( ) }
997+ return (
998+ < Show when = { retry ( ) } >
999+ < box onMouseUp = { handleMessageClick } >
1000+ < text fg = { theme . error } > { retryText ( ) } </ text >
1001+ </ box >
1002+ </ Show >
1003+ )
1004+ } ) ( ) }
1005+ </ box >
9561006 </ box >
1007+ < text fg = { store . interrupt > 0 ? theme . primary : theme . text } >
1008+ esc{ " " }
1009+ < span style = { { fg : store . interrupt > 0 ? theme . primary : theme . textMuted } } >
1010+ { store . interrupt > 0 ? "again to interrupt" : "interrupt" }
1011+ </ span >
1012+ </ text >
9571013 </ box >
958- < text fg = { store . interrupt > 0 ? theme . primary : theme . text } >
959- esc{ " " }
960- < span style = { { fg : store . interrupt > 0 ? theme . primary : theme . textMuted } } >
961- { store . interrupt > 0 ? "again to interrupt" : "interrupt" }
962- </ span >
963- </ text >
964- </ box >
965- </ Show >
966- < Show when = { status ( ) . type !== "retry" } >
967- < box gap = { 2 } flexDirection = "row" >
968- < Switch >
969- < Match when = { store . mode === "normal" } >
1014+ </ Show >
1015+ < Show when = { ! props . sessionID && status ( ) . type === "idle" && shortcutsVisible ( ) } >
1016+ < box flexDirection = "row" justifyContent = "flex-end" flexGrow = { 1 } >
1017+ < box gap = { 2 } flexDirection = "row" >
9701018 < text fg = { theme . text } >
9711019 { keybind . print ( "agent_cycle" ) } < span style = { { fg : theme . textMuted } } > switch agent</ span >
9721020 </ text >
9731021 < text fg = { theme . text } >
9741022 { keybind . print ( "command_list" ) } < span style = { { fg : theme . textMuted } } > commands</ span >
9751023 </ text >
976- </ Match >
977- < Match when = { store . mode === "shell" } >
1024+ </ box >
1025+ </ box >
1026+ </ Show >
1027+ < Show when = { props . sessionID && props . footerVisible && status ( ) . type === "idle" && shortcutsVisible ( ) } >
1028+ < box flexDirection = "row" justifyContent = "flex-end" flexGrow = { 1 } >
1029+ < box gap = { 2 } flexDirection = "row" >
9781030 < text fg = { theme . text } >
979- esc < span style = { { fg : theme . textMuted } } > exit shell mode </ span >
1031+ { keybind . print ( "agent_cycle" ) } < span style = { { fg : theme . textMuted } } > switch agent </ span >
9801032 </ text >
981- </ Match >
982- </ Switch >
983- </ box >
984- </ Show >
985- </ box >
1033+ < text fg = { theme . text } >
1034+ { keybind . print ( "command_list" ) } < span style = { { fg : theme . textMuted } } > commands</ span >
1035+ </ text >
1036+ </ box >
1037+ </ box >
1038+ </ Show >
1039+ </ box >
1040+ </ Show >
9861041 </ box >
9871042 </ >
9881043 )
0 commit comments