char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
3d23fd2
main
1import { Signal } from "@char/aftercare" ; 2import type { InboxItem } from "@earendil-works/pi-durable" ; 3import type { UserMessage } from "@earendil-works/pi-ai" ; 4import { imageSummary } from "@berry/api/images.ts" ; 5import { storedDraft } from "./draft-storage.ts" ; 6import { keyed , type Scope } from "./ui.ts" ; 7 8export type StoppedInput = Extract < InboxItem , { content :unknown }>; 9 10class StoppedInputs { 11readonly items = new Signal < readonly StoppedInput []>([]); 12readonly ready :Promise < void >; 13 #saving= Promise . resolve (); 14 15constructor ( readonly tid :string ) { 16this . ready = typeof indexedDB === "undefined" 17 ?Promise . resolve () 18 :storedDraft < StoppedInput >( `stopped: ${ tid } ` ). then (( items ) => this . items . set ( items )); 19} 20 21save ( items :readonly StoppedInput []) :Promise < void > { 22this . items . set ( items ); 23if ( typeof indexedDB === "undefined" ) return Promise . resolve (); 24const saved = this . #saving. then (() => storedDraft ( `stopped: ${ this . tid } ` , items )). then (() => {}); 25this . #saving= saved . catch (() => {}); 26return saved ; 27} 28} 29 30const drafts = new Map < string , StoppedInputs >(); 31 32/** Aborted inputs are drafts, never members of the conversation's live queue. */ 33export function stoppedInputs ( 34scope :Scope , 35tid :string , 36restore :( content :UserMessage [ "content" ]) => Promise < boolean >, 37notify :( message :string , error ?:boolean ) => void , 38) { 39if ( ! drafts . has ( tid )) drafts . set ( tid , new StoppedInputs ( tid )); 40const draft = drafts . get ( tid ) ! ; 41let disposed = false ; 42let pending = false ; 43const element = < div class = "inbox stopped-inputs" aria - label = "Stopped messages" / > as HTMLElement ; 44const restoreInput = async ( item :StoppedInput ) => { 45if ( pending || disposed ) return ; 46pending = true ; 47try { 48if ( await restore ( item . content ) ) { 49await draft . save ( draft . items . get (). filter (({ id }) => id !== item . id )); 50} else { 51notify ( "Send or clear your current draft before restoring a stopped message." ); 52} 53} finally { 54pending = false ; 55} 56}; 57const report = ( error : unknown ) => 58notify ( error instanceof Error ? error . message : String ( error ), true ); 59const rows = keyed ( element , ( item : StoppedInput ) => item . id , ( item ) => ({ 60node : ( 61< div class = "queued" > 62< div > 63< span class = "mode" > stopped < / span>{" "} 64< span class = "queued-content" > 65{ typeof item . content === "string" 66? item . content 67: item . content . flatMap (( block ) => block . type === "text" ? [ block . text ] : []). join ( 68"\n" , 69)} 70< / span>{" "} 71< span class = "queued-images" >{ imageSummary ( item . content )}< /span> 72</ div > 73< div class = "queued-actions" > 74< button type = "button" _onclick = {() => void restoreInput ( item ). catch ( report )}> 75Restore 76</ button > 77< button 78type = "button" 79_onclick = {() => { 80if ( ! pending ) { 81void draft . save ( draft . items . get (). filter (({ id }) => id !== item . id )). catch ( report ); 82} 83}} 84> 85Dismiss 86< /button> 87</ div > 88< /div> 89), 90})); 91scope . add ( 92draft . items . subscribeImmediate (( items ) => { 93element . hidden = items . length === 0 ; 94rows . sync ( items ); 95}). unsubscribe , 96); 97scope . add ( rows . dispose ); 98scope . add (() => disposed = true ); 99void draft . ready . catch ( report ); 100return { 101element , 102async recover ( inputs : readonly StoppedInput []) { 103if ( inputs . length === 0 ) return ; 104await draft . ready . catch (() => {}); 105const ordered = [ ... inputs ]. sort (( a , b ) => 106Number ( a . mode === "followUp" ) - Number ( b . mode === "followUp" ) 107); 108const ids = new Set ( draft . items . get (). map (({ id }) => id )); 109await draft . save ([ ... draft . items . get (), ... ordered . filter (({ id }) => ! ids . has ( id ))]); 110await restoreInput ( ordered [ 0 ]); 111}, 112}; 113}