@@ -55,6 +55,59 @@ type Overlay =
5555
5656type LayoutMode = "dashboard" | "classic" ;
5757
58+ function ThinkingSpinner ( ) {
59+ const [ frame , setFrame ] = useState ( 0 ) ;
60+ const frames = [ "⠋" , "⠙" , "⠹" , "⠸" , "⠼" , "⠴" , "⠦" , "⠧" , "⠇" , "⠏" ] ;
61+
62+ useEffect ( ( ) => {
63+ const timer = setInterval ( ( ) => {
64+ setFrame ( ( f ) => ( f + 1 ) % frames . length ) ;
65+ } , 80 ) ;
66+ return ( ) => clearInterval ( timer ) ;
67+ } , [ ] ) ;
68+
69+ return < Text color = "cyan" bold > { frames [ frame ] } </ Text > ;
70+ }
71+
72+ function renderMessageText ( text : string , panelWidth : number ) {
73+ if ( ! text ) return null ;
74+ const parts = text . split ( "```" ) ;
75+ return parts . map ( ( part , index ) => {
76+ const isCode = index % 2 === 1 ;
77+ if ( isCode ) {
78+ const lines = part . split ( "\n" ) ;
79+ const firstLine = lines [ 0 ] || "" ;
80+ const hasLang = / ^ [ a - z A - Z 0 - 9 _ - ] + $ / . test ( firstLine . trim ( ) ) ;
81+ const codeLines = hasLang ? lines . slice ( 1 ) : lines ;
82+ const codeText = codeLines . join ( "\n" ) . trim ( ) ;
83+ return (
84+ < Box
85+ key = { index }
86+ flexDirection = "column"
87+ borderStyle = "single"
88+ borderColor = "gray"
89+ paddingX = { 1 }
90+ marginY = { 1 }
91+ width = { Math . max ( 20 , panelWidth - 8 ) }
92+ >
93+ { hasLang && (
94+ < Text color = "cyan" bold dimColor >
95+ { `/* ${ firstLine . trim ( ) } */` }
96+ </ Text >
97+ ) }
98+ < Text color = "yellow" > { codeText } </ Text >
99+ </ Box >
100+ ) ;
101+ } else {
102+ return (
103+ < Text key = { index } color = "white" >
104+ { part }
105+ </ Text >
106+ ) ;
107+ }
108+ } ) ;
109+ }
110+
58111function emptyAgent ( id : AgentId ) : AgentPaneModel {
59112 return {
60113 id,
@@ -79,6 +132,7 @@ export function App(props: {
79132 const [ cols , setCols ] = useState ( stdout ?. columns || 120 ) ;
80133 const [ rows , setRows ] = useState ( stdout ?. rows || 40 ) ;
81134 const [ layoutMode , setLayoutMode ] = useState < LayoutMode > ( "classic" ) ; // Default is simple/classic chat interface!
135+ const [ attachedFiles , setAttachedFiles ] = useState < { path : string ; content : string } [ ] > ( [ ] ) ;
82136 const [ chatLines , setChatLines ] = useState < { id : string ; sender : string ; text : string ; color ?: string } [ ] > ( [
83137 { id : "welcome-1" , sender : "system" , text : "Welcome to ArrowCode Classic Mode!" , color : "cyan" } ,
84138 { id : "welcome-2" , sender : "system" , text : "Type any prompt or /help to explore. Toggle layout with /layout." , color : "gray" } ,
@@ -366,22 +420,65 @@ export function App(props: {
366420 }
367421 } ) ;
368422
369- const onSubmit = async ( line : string ) => {
423+ const onSubmit = async ( inputLine : string ) => {
424+ let line = inputLine . trim ( ) ;
425+ if ( line . startsWith ( "." ) ) {
426+ line = "/" + line . slice ( 1 ) ;
427+ }
428+
370429 // Save to user chat log too
371430 setChatLines ( ( prev ) => [
372431 ...prev . slice ( - 100 ) ,
373432 {
374433 id : `user-${ Date . now ( ) } -${ Math . random ( ) } ` ,
375434 sender : "user" ,
376- text : line ,
435+ text : inputLine ,
377436 color : "white" ,
378437 } ,
379438 ] ) ;
380439
440+ if ( line . startsWith ( "/add " ) ) {
441+ const filePath = line . slice ( 5 ) . trim ( ) ;
442+ try {
443+ const absolutePath = harness . workspace . resolve ( filePath , { mustExist : true } ) ;
444+ const fs = require ( "node:fs" ) ;
445+ const content = fs . readFileSync ( absolutePath , "utf8" ) ;
446+ const lineCount = content . split ( "\n" ) . length ;
447+ setAttachedFiles ( ( prev ) => [ ...prev , { path : filePath , content } ] ) ;
448+ setSystemLine ( `Attached file ${ filePath } (${ lineCount } lines)` ) ;
449+ setChatLines ( ( prev ) => [
450+ ...prev ,
451+ {
452+ id : `add-${ Date . now ( ) } ` ,
453+ sender : "system" ,
454+ text : `📎 Attached file: ${ filePath } (${ lineCount } lines). This will be included in your next prompt/plan.` ,
455+ color : "green" ,
456+ } ,
457+ ] ) ;
458+ } catch ( err : any ) {
459+ setSystemLine ( `Error reading file: ${ err . message } ` ) ;
460+ setChatLines ( ( prev ) => [
461+ ...prev ,
462+ {
463+ id : `add-err-${ Date . now ( ) } ` ,
464+ sender : "system" ,
465+ text : `❌ Failed to attach file "${ filePath } ": ${ err . message } ` ,
466+ color : "red" ,
467+ } ,
468+ ] ) ;
469+ }
470+ return ;
471+ }
472+
381473 if ( line . startsWith ( "/" ) ) {
382474 // local dashboard commands
383475 const [ cmd , ...rest ] = line . slice ( 1 ) . split ( / \s + / ) ;
384476 const c = ( cmd || "" ) . toLowerCase ( ) ;
477+ if ( c === "swarm" ) {
478+ setLayoutMode ( ( prev ) => ( prev === "dashboard" ? "classic" : "dashboard" ) ) ;
479+ setSystemLine ( `Layout switched to ${ layoutMode === "dashboard" ? "classic" : "dashboard" } ` ) ;
480+ return ;
481+ }
385482 if ( c === "replay" ) {
386483 const path = harness . exportReplay ( rest [ 0 ] ) ;
387484 setSystemLine ( `Replay exported → ${ path } ` ) ;
@@ -404,7 +501,16 @@ export function App(props: {
404501 return ;
405502 }
406503
407- const res = await dispatchCommand ( line , {
504+ let cmdLine = line ;
505+ if ( c === "plan" && attachedFiles . length > 0 ) {
506+ const attachmentBlock = attachedFiles
507+ . map ( ( f ) => `=== ATTACHED FILE: ${ f . path } ===\n${ f . content } \n===============================` )
508+ . join ( "\n\n" ) ;
509+ cmdLine = `/plan ${ attachmentBlock } \n\nUser Request:\n${ line . slice ( 5 ) . trim ( ) } ` ;
510+ setAttachedFiles ( [ ] ) ;
511+ }
512+
513+ const res = await dispatchCommand ( cmdLine , {
408514 harness,
409515 setYolo,
410516 getYolo : ( ) => yolo ,
@@ -444,7 +550,16 @@ export function App(props: {
444550 refreshFiles ( ) ;
445551 return ;
446552 }
447- harness . chat ( line ) ;
553+
554+ let payload = line ;
555+ if ( attachedFiles . length > 0 ) {
556+ const attachmentBlock = attachedFiles
557+ . map ( ( f ) => `=== ATTACHED FILE: ${ f . path } ===\n${ f . content } \n===============================` )
558+ . join ( "\n\n" ) ;
559+ payload = `${ attachmentBlock } \n\nUser Prompt:\n${ line } ` ;
560+ setAttachedFiles ( [ ] ) ;
561+ }
562+ harness . chat ( payload ) ;
448563 } ;
449564
450565 const onSettingsSave = ( v : SettingsValues ) => {
@@ -721,74 +836,28 @@ export function App(props: {
721836 </ Box >
722837 ) : (
723838 /* CLASSIC SIMPLE CHAT MODE */
724- < Box flexDirection = "row " height = { midH } >
725- { /* LEFT CHAT PANEL: Beautiful classic scrollable Chat stream */ }
839+ < Box flexDirection = "column " height = { midH } >
840+ { /* FULL SCREEN CHAT PANEL: Beautiful classic scrollable Chat stream */ }
726841 < Box
727842 flexDirection = "column"
728- width = { leftW }
843+ width = { width }
729844 height = { midH }
730845 borderStyle = "single"
731846 borderColor = "gray"
732847 paddingX = { 1 }
733848 >
734849 < Box flexDirection = "column" height = { midH - 2 } >
735- { chatLines . slice ( - Math . floor ( midH / 2 ) ) . map ( ( line ) => (
736- < Text key = { line . id } >
850+ { chatLines . slice ( - 10 ) . map ( ( line ) => (
851+ < Box key = { line . id } flexDirection = "column" marginY = { 0 } paddingBottom = { 1 } >
737852 < Text color = { line . color || "white" } bold >
738853 { `[${ line . sender } ]` }
739854 </ Text >
740- < Text color = "white" > { line . text . slice ( 0 , leftW - 12 ) } </ Text >
741- </ Text >
742- ) ) }
743- </ Box >
744- </ Box >
745-
746- { /* RIGHT SIDEBAR: Sleek classic high-contrast status card */ }
747- < Box
748- flexDirection = "column"
749- width = { rightW }
750- height = { midH }
751- borderStyle = "single"
752- borderColor = "cyan"
753- paddingX = { 1 }
754- >
755- < Text color = "cyan" bold > ┌── STATUS PANEL ──┐</ Text >
756- < Box flexDirection = "column" marginTop = { 1 } >
757- < Text >
758- < Text color = "gray" > Workspace: </ Text >
759- < Text color = "white" > { config . workspace . split ( "/" ) . pop ( ) || "root" } </ Text >
760- </ Text >
761- < Text >
762- < Text color = "gray" > Provider: </ Text >
763- < Text color = "magenta" > { config . provider } </ Text >
764- </ Text >
765- < Text >
766- < Text color = "gray" > Active: </ Text >
767- < Text color = "yellow" > { phase . toUpperCase ( ) } </ Text >
768- </ Text >
769- < Text >
770- < Text color = "gray" > Swarm: </ Text >
771- < Text color = "green" > { `${ swarmActive } / ${ config . swarm ?. maxWorkers ?? 16 } ` } </ Text >
772- </ Text >
773- </ Box >
774-
775- < Box flexDirection = "column" marginTop = { 1 } borderStyle = "single" borderColor = "gray" >
776- < Text color = "cyan" bold > SQUAD STATUS </ Text >
777- { panes . map ( ( p ) => (
778- < Text key = { p . id } >
779- < Text color = "gray" > { p . id . toUpperCase ( ) . slice ( 0 , 4 ) } : </ Text >
780- < Text color = { p . status === "thinking" ? "yellow" : "green" } >
781- { p . status . toUpperCase ( ) }
782- </ Text >
783- </ Text >
855+ < Box flexDirection = "column" paddingLeft = { 2 } >
856+ { renderMessageText ( line . text , width ) }
857+ </ Box >
858+ </ Box >
784859 ) ) }
785860 </ Box >
786-
787- < Box marginTop = { 1 } >
788- < Text color = "gray" dimColor >
789- Toggle UI mode with: /layout
790- </ Text >
791- </ Box >
792861 </ Box >
793862 </ Box >
794863 ) }
@@ -803,10 +872,16 @@ export function App(props: {
803872 < Timeline events = { timeline } width = { timeW } height = { bottomH } />
804873 </ Box >
805874
806- < Box paddingX = { 1 } width = { width } >
875+ < Box paddingX = { 1 } width = { width } flexDirection = "row" justifyContent = "space-between" >
807876 < Text color = "gray" wrap = "truncate" >
808877 { systemLine }
809878 </ Text >
879+ { runActive && (
880+ < Box flexDirection = "row" >
881+ < ThinkingSpinner />
882+ < Text color = "cyan" bold > Thinking & executing tools ...</ Text >
883+ </ Box >
884+ ) }
810885 </ Box >
811886
812887 { finalText ? (
0 commit comments