22import React , { useState , useRef } from "react" ;
33import { FaUpload , FaFileAlt , FaHistory , FaSpinner } from "react-icons/fa" ;
44import { essaysEngine } from "@/utils/api" ;
5+ import { v4 as uuidv4 } from 'uuid' ;
6+ import ReactMarkdown from 'react-markdown' ;
7+ import remarkGfm from 'remark-gfm' ;
58
9+ // Backend /assemblies returns Swarm objects: { id, topic_name, agents }
10+ // We adapt to what the UI needs by adding friendly name/description fields.
611interface EssayCase {
712 id : string ;
8- name : string ;
9- description : string ;
13+ topic_name : string ; // original backend field used as the display label
14+ agents : any [ ] ;
15+ name ?: string ; // derived (topic_name)
16+ description ?: string ; // currently not provided by backend
17+ content ?: string ; // reference text / prompt body
18+ explanation ?: string ; // instructions / rubric
1019}
1120
1221interface Evaluation {
@@ -36,33 +45,85 @@ const EssaySubmission: React.FC = () => {
3645 const [ loadingCases , setLoadingCases ] = useState ( true ) ;
3746 const [ loadingHistory , setLoadingHistory ] = useState ( true ) ;
3847 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
48+ const [ essayLookup , setEssayLookup ] = useState < Record < string , any > > ( { } ) ;
3949
40- // Load available essay cases
50+ // Load available essay cases (assemblies)
4151 React . useEffect ( ( ) => {
4252 setLoadingCases ( true ) ;
4353 essaysEngine . get ( "/assemblies" )
4454 . then ( res => {
45- // Failsafe: ensure data is an array
46- const content = Array . isArray ( res . data ?. content ) ? res . data . content : [ ] ;
47- setCases ( content ) ;
55+ const raw = Array . isArray ( res . data ?. content ) ? res . data . content : [ ] ;
56+ const mapped : EssayCase [ ] = raw . map ( ( r : any ) => ( {
57+ id : r . id ,
58+ topic_name : r . topic_name ,
59+ // Provide fallbacks so UI never breaks
60+ agents : r . agents || [ ] ,
61+ name : r . topic_name ,
62+ description : r . description || ""
63+ } ) ) ;
64+ if ( mapped . length === 0 ) {
65+ // Fallback: try to derive cases from existing essays if assemblies not seeded yet
66+ return essaysEngine . get ( '/essays' ) . then ( er => {
67+ const essays = Array . isArray ( er . data ?. content ) ? er . data . content : [ ] ;
68+ const essayCases : EssayCase [ ] = essays . map ( ( e : any ) => ( {
69+ id : e . id ,
70+ topic_name : e . topic || e . theme || e . id ,
71+ agents : [ ] ,
72+ name : e . topic || e . theme || e . id ,
73+ description : e . explanation || '' ,
74+ content : e . content ,
75+ explanation : e . explanation
76+ } ) ) ;
77+ setCases ( essayCases ) ;
78+ const lookup : Record < string , any > = { } ;
79+ essays . forEach ( ( e : any ) => { lookup [ e . id ] = e ; } ) ;
80+ setEssayLookup ( lookup ) ;
81+ } ) . catch ( fallbackErr => {
82+ console . error ( 'Fallback /essays fetch failed:' , fallbackErr ) ;
83+ setCases ( [ ] ) ;
84+ } ) ;
85+ }
86+ setCases ( mapped ) ;
87+ } )
88+ . catch ( err => {
89+ console . error ( 'Failed loading /assemblies:' , err ) ;
90+ setCases ( [ ] ) ;
4891 } )
49- . catch ( ( ) => setCases ( [ ] ) )
5092 . finally ( ( ) => setLoadingCases ( false ) ) ;
5193 } , [ ] ) ;
5294
53- // Load submission history
95+ // Load submission history (using /resources now, since submissions are stored as resources)
5496 React . useEffect ( ( ) => {
5597 setLoadingHistory ( true ) ;
56- essaysEngine . get ( "/essays " )
98+ essaysEngine . get ( "/resources " )
5799 . then ( res => {
58- // Failsafe: ensure data is an array
59100 const content = Array . isArray ( res . data ?. content ) ? res . data . content : [ ] ;
60- setHistory ( content ) ;
101+ const mapped : Submission [ ] = content . map ( ( r : any ) => ( {
102+ id : r . id ,
103+ essayText : r . content ,
104+ description : Array . isArray ( r . objective ) ? r . objective . slice ( 1 ) . join ( ' ' ) : '' ,
105+ submittedAt : r . submittedAt || '' , // backend currently does not send this
106+ essayFileName : undefined ,
107+ evaluations : [ ]
108+ } ) ) ;
109+ setHistory ( mapped ) ;
61110 } )
62111 . catch ( ( ) => setHistory ( [ ] ) )
63112 . finally ( ( ) => setLoadingHistory ( false ) ) ;
64113 } , [ ] ) ;
65114
115+ // Independently load essays to enrich lookup for markdown reference/instructions
116+ React . useEffect ( ( ) => {
117+ essaysEngine . get ( '/essays' )
118+ . then ( res => {
119+ const items = Array . isArray ( res . data ?. content ) ? res . data . content : [ ] ;
120+ const lookup : Record < string , any > = { } ;
121+ items . forEach ( ( e : any ) => { lookup [ e . id ] = e ; } ) ;
122+ setEssayLookup ( lookup ) ;
123+ } )
124+ . catch ( err => console . warn ( 'Could not load essays for reference text:' , err ) ) ;
125+ } , [ ] ) ;
126+
66127 const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
67128 if ( e . target . files && e . target . files [ 0 ] ) {
68129 setEssayFile ( e . target . files [ 0 ] ) ;
@@ -74,16 +135,38 @@ const EssaySubmission: React.FC = () => {
74135 if ( ! selectedCase || ( ! essayText && ! essayFile ) ) return ;
75136 setSubmitting ( true ) ;
76137 setEvaluations ( [ ] ) ;
77- const formData = new FormData ( ) ;
78- formData . append ( "caseId" , selectedCase . id ) ;
79- formData . append ( "description" , description ) ;
80- if ( essayText ) formData . append ( "essayText" , essayText ) ;
81- if ( essayFile ) formData . append ( "essayFile" , essayFile ) ;
82- const res = await essaysEngine . post ( "/essays" , formData ) ;
83- const data = res . data ;
84- setEvaluations ( data . evaluations || [ ] ) ;
85- setHistory ( h => [ data . content , ...h ] ) ;
86- setSubmitting ( false ) ;
138+
139+ // NOTE: Backend /resources expects: id, objective (List[str]), content?, essay_id, url?.
140+ // We treat a submission as a Resource referencing the selectedCase (assembly) id.
141+ const resourcePayload = {
142+ id : uuidv4 ( ) ,
143+ objective : description ? [ "student_submission" , description ] : [ "student_submission" ] ,
144+ content : essayText || ( essayFile ? `(uploaded file: ${ essayFile . name } )` : '' ) ,
145+ essay_id : selectedCase . id ,
146+ url : undefined
147+ } ;
148+
149+ try {
150+ const res = await essaysEngine . post ( "/resources" , resourcePayload ) ;
151+ // We don't receive evaluations from this endpoint; keep empty for now.
152+ const submission : Submission = {
153+ id : resourcePayload . id ,
154+ essayText : resourcePayload . content ,
155+ essayFileName : essayFile ?. name ,
156+ description : description ,
157+ submittedAt : new Date ( ) . toISOString ( ) ,
158+ evaluations : [ ]
159+ } ;
160+ setHistory ( h => [ submission , ...h ] ) ;
161+ // Clear form
162+ setEssayText ( '' ) ;
163+ setEssayFile ( null ) ;
164+ setDescription ( '' ) ;
165+ } catch ( err ) {
166+ console . error ( 'Failed to submit resource' , err ) ;
167+ } finally {
168+ setSubmitting ( false ) ;
169+ }
87170 } ;
88171
89172 if ( loadingCases ) {
@@ -120,12 +203,31 @@ const EssaySubmission: React.FC = () => {
120203 >
121204 < option value = "" > -- 🚀 Choose your challenge --</ option >
122205 { cases . map ( ca => (
123- < option key = { ca . id } value = { ca . id } > 📝 { ca . name || "Untitled" } </ option >
206+ < option key = { ca . id } value = { ca . id } > 📝 { ca . name || ca . topic_name || "Untitled" } </ option >
124207 ) ) }
125208 </ select >
126209 ) }
127- { selectedCase && < div className = "mt-2 text-cyan-700 text-base italic" > { selectedCase . description || "No description." } </ div > }
128210 </ div >
211+ { selectedCase && (
212+ < div className = "mb-8 grid grid-cols-1 md:grid-cols-2 gap-6" >
213+ < div className = "rounded-2xl border-2 border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/40 p-4 overflow-y-auto max-h-[360px] prose prose-sm md:prose-base dark:prose-invert" >
214+ < h3 className = "text-lg font-bold mb-2" > 📄 Reference Text</ h3 >
215+ < div className = "break-words" >
216+ < ReactMarkdown remarkPlugins = { [ remarkGfm ] } >
217+ { ( essayLookup [ selectedCase . id ] ?. content ) || selectedCase . content || 'No reference text available.' }
218+ </ ReactMarkdown >
219+ </ div >
220+ </ div >
221+ < div className = "rounded-2xl border-2 border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/40 p-4 overflow-y-auto max-h-[360px] prose prose-sm md:prose-base dark:prose-invert" >
222+ < h3 className = "text-lg font-bold mb-2" > 🧭 Instructions / Rubric</ h3 >
223+ < div className = "break-words" >
224+ < ReactMarkdown remarkPlugins = { [ remarkGfm ] } >
225+ { ( essayLookup [ selectedCase . id ] ?. explanation ) || selectedCase . explanation || selectedCase . description || 'No instructions available.' }
226+ </ ReactMarkdown >
227+ </ div >
228+ </ div >
229+ </ div >
230+ ) }
129231 { /* Essay submission form */ }
130232 < form onSubmit = { handleSubmit } className = "bg-white dark:bg-boxdark rounded-2xl shadow-lg p-8 mb-8 flex flex-col gap-6 border-2 border-cyan-100 dark:border-cyan-800" >
131233 < label className = "font-semibold flex items-center gap-2 text-green-700" >
0 commit comments