Reubencf commited on
Commit
75d362b
Β·
1 Parent(s): 992b46f

changing background

Browse files
app/api/download/route.ts CHANGED
@@ -66,7 +66,19 @@ export async function GET(request: NextRequest) {
66
  '.mp3': 'audio/mpeg',
67
  '.mp4': 'video/mp4',
68
  '.zip': 'application/zip',
69
- '.rar': 'application/x-rar-compressed'
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
71
 
72
  if (mimeTypes[ext]) {
 
66
  '.mp3': 'audio/mpeg',
67
  '.mp4': 'video/mp4',
68
  '.zip': 'application/zip',
69
+ '.rar': 'application/x-rar-compressed',
70
+ '.tex': 'text/x-tex',
71
+ '.latex': 'text/x-latex',
72
+ '.dart': 'text/x-dart',
73
+ '.flutter': 'text/x-dart',
74
+ '.yaml': 'text/yaml',
75
+ '.yml': 'text/yaml',
76
+ '.xml': 'text/xml',
77
+ '.csv': 'text/csv',
78
+ '.rtf': 'application/rtf',
79
+ '.odt': 'application/vnd.oasis.opendocument.text',
80
+ '.ods': 'application/vnd.oasis.opendocument.spreadsheet',
81
+ '.odp': 'application/vnd.oasis.opendocument.presentation'
82
  }
83
 
84
  if (mimeTypes[ext]) {
app/api/sessions/download/route.ts CHANGED
@@ -77,11 +77,39 @@ export async function GET(request: NextRequest) {
77
  'html': 'text/html',
78
  'css': 'text/css',
79
  'js': 'application/javascript',
 
 
 
80
  'png': 'image/png',
81
  'jpg': 'image/jpeg',
82
  'jpeg': 'image/jpeg',
83
  'gif': 'image/gif',
84
- 'svg': 'image/svg+xml'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  };
86
 
87
  if (ext && contentTypes[ext]) {
 
77
  'html': 'text/html',
78
  'css': 'text/css',
79
  'js': 'application/javascript',
80
+ 'ts': 'text/typescript',
81
+ 'jsx': 'text/javascript',
82
+ 'tsx': 'text/typescript',
83
  'png': 'image/png',
84
  'jpg': 'image/jpeg',
85
  'jpeg': 'image/jpeg',
86
  'gif': 'image/gif',
87
+ 'svg': 'image/svg+xml',
88
+ 'tex': 'text/x-tex',
89
+ 'latex': 'text/x-latex',
90
+ 'dart': 'text/x-dart',
91
+ 'flutter': 'text/x-dart',
92
+ 'yaml': 'text/yaml',
93
+ 'yml': 'text/yaml',
94
+ 'xml': 'text/xml',
95
+ 'csv': 'text/csv',
96
+ 'md': 'text/markdown',
97
+ 'py': 'text/x-python',
98
+ 'java': 'text/x-java',
99
+ 'cpp': 'text/x-c++',
100
+ 'c': 'text/x-c',
101
+ 'h': 'text/x-c',
102
+ 'hpp': 'text/x-c++',
103
+ 'zip': 'application/zip',
104
+ 'rar': 'application/x-rar-compressed',
105
+ 'mp3': 'audio/mpeg',
106
+ 'mp4': 'video/mp4',
107
+ 'avi': 'video/x-msvideo',
108
+ 'mov': 'video/quicktime',
109
+ 'rtf': 'application/rtf',
110
+ 'odt': 'application/vnd.oasis.opendocument.text',
111
+ 'ods': 'application/vnd.oasis.opendocument.spreadsheet',
112
+ 'odp': 'application/vnd.oasis.opendocument.presentation'
113
  };
114
 
115
  if (ext && contentTypes[ext]) {
app/components/Desktop.tsx CHANGED
@@ -59,7 +59,7 @@ export function Desktop() {
59
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
60
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
61
  const [backgroundSelectorOpen, setBackgroundSelectorOpen] = useState(false)
62
- const [currentBackground, setCurrentBackground] = useState('gradient-sonoma')
63
  const [aboutModalOpen, setAboutModalOpen] = useState(false)
64
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
65
  const [flutterRunnerOpen, setFlutterRunnerOpen] = useState(false)
 
59
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
60
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
61
  const [backgroundSelectorOpen, setBackgroundSelectorOpen] = useState(false)
62
+ const [currentBackground, setCurrentBackground] = useState('https://images.unsplash.com/photo-1545159639-3f3534aa074e')
63
  const [aboutModalOpen, setAboutModalOpen] = useState(false)
64
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
65
  const [flutterRunnerOpen, setFlutterRunnerOpen] = useState(false)
app/components/FileManager.tsx CHANGED
@@ -71,7 +71,7 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
71
  const [uploadModalOpen, setUploadModalOpen] = useState(false)
72
  const [previewFile, setPreviewFile] = useState<FileItem | null>(null)
73
  const [isPublicFolder, setIsPublicFolder] = useState(false)
74
- const [sidebarSelection, setSidebarSelection] = useState('documents') // 'applications', 'desktop', 'documents', 'downloads', 'public'
75
 
76
  // Load files when path changes or sessionId becomes available
77
  useEffect(() => {
@@ -83,12 +83,8 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
83
  setSidebarSelection('public')
84
  } else if (currentPath === 'Applications') {
85
  setSidebarSelection('applications')
86
- } else if (currentPath === 'Desktop') {
87
- setSidebarSelection('desktop')
88
- } else if (currentPath === 'Downloads') {
89
- setSidebarSelection('downloads')
90
  } else {
91
- setSidebarSelection('documents')
92
  }
93
 
94
  // Only load files if we have a sessionId (for non-public folders) or if it's a public folder
@@ -196,7 +192,21 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
196
  }
197
 
198
  const handleDownload = (file: FileItem) => {
199
- window.open(`/api/download?path=${encodeURIComponent(file.path)}`, '_blank')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
  const handlePreview = (file: FileItem) => {
@@ -317,12 +327,8 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
317
  setSidebarSelection(item)
318
  if (item === 'applications') {
319
  onNavigate('Applications')
320
- } else if (item === 'desktop') {
321
- onNavigate('Desktop')
322
- } else if (item === 'documents') {
323
  onNavigate('')
324
- } else if (item === 'downloads') {
325
- onNavigate('Downloads')
326
  } else if (item === 'public') {
327
  onNavigate('public')
328
  }
@@ -400,39 +406,25 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
400
  </div>
401
  <nav className="space-y-1 px-2">
402
  <button
403
- onClick={() => handleSidebarClick('applications')}
404
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'applications' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
405
- >
406
- <AppWindow size={18} weight="fill" className="text-blue-500" />
407
- Applications
408
- </button>
409
- <button
410
- onClick={() => handleSidebarClick('desktop')}
411
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'desktop' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
412
- >
413
- <Desktop size={18} weight="fill" className="text-cyan-500" />
414
- Desktop
415
- </button>
416
- <button
417
- onClick={() => handleSidebarClick('documents')}
418
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'documents' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
419
  >
420
- <FileText size={18} weight="fill" className="text-gray-500" />
421
- Documents
422
  </button>
423
  <button
424
- onClick={() => handleSidebarClick('downloads')}
425
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'downloads' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
426
  >
427
- <DownloadSimple size={18} weight="fill" className="text-blue-500" />
428
- Downloads
429
  </button>
430
  <button
431
- onClick={() => handleSidebarClick('public')}
432
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'public' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
433
  >
434
- <Users size={18} weight="fill" className="text-purple-500" />
435
- Public
436
  </button>
437
  </nav>
438
  </div>
@@ -533,7 +525,18 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
533
  onNavigate(file.path)
534
  } else if (file.type === 'flutter_app' && onOpenFlutterApp) {
535
  onOpenFlutterApp(file)
 
 
 
 
 
 
 
 
 
 
536
  } else {
 
537
  handlePreview(file)
538
  }
539
  }}
 
71
  const [uploadModalOpen, setUploadModalOpen] = useState(false)
72
  const [previewFile, setPreviewFile] = useState<FileItem | null>(null)
73
  const [isPublicFolder, setIsPublicFolder] = useState(false)
74
+ const [sidebarSelection, setSidebarSelection] = useState('myfiles') // 'myfiles', 'public', 'applications'
75
 
76
  // Load files when path changes or sessionId becomes available
77
  useEffect(() => {
 
83
  setSidebarSelection('public')
84
  } else if (currentPath === 'Applications') {
85
  setSidebarSelection('applications')
 
 
 
 
86
  } else {
87
+ setSidebarSelection('myfiles')
88
  }
89
 
90
  // Only load files if we have a sessionId (for non-public folders) or if it's a public folder
 
192
  }
193
 
194
  const handleDownload = (file: FileItem) => {
195
+ // Check if it's a public file or session file
196
+ if (isPublicFolder) {
197
+ // For public files, use the sessions/download endpoint
198
+ window.open(`/api/sessions/download?file=${encodeURIComponent(file.name)}&public=true`, '_blank')
199
+ } else if (sessionId) {
200
+ // For session files, we need to pass the session ID
201
+ // Create a temporary form to submit with headers
202
+ const link = document.createElement('a')
203
+ link.href = `/api/sessions/download?file=${encodeURIComponent(file.name)}`
204
+ link.download = file.name
205
+ link.click()
206
+ } else {
207
+ // Fallback to the old API for backward compatibility
208
+ window.open(`/api/download?path=${encodeURIComponent(file.path)}`, '_blank')
209
+ }
210
  }
211
 
212
  const handlePreview = (file: FileItem) => {
 
327
  setSidebarSelection(item)
328
  if (item === 'applications') {
329
  onNavigate('Applications')
330
+ } else if (item === 'myfiles') {
 
 
331
  onNavigate('')
 
 
332
  } else if (item === 'public') {
333
  onNavigate('public')
334
  }
 
406
  </div>
407
  <nav className="space-y-1 px-2">
408
  <button
409
+ onClick={() => handleSidebarClick('myfiles')}
410
+ className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'myfiles' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  >
412
+ <FolderIcon size={18} weight="fill" className="text-blue-500" />
413
+ My Files
414
  </button>
415
  <button
416
+ onClick={() => handleSidebarClick('public')}
417
+ className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'public' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
418
  >
419
+ <Globe size={18} weight="fill" className="text-purple-500" />
420
+ Public Files
421
  </button>
422
  <button
423
+ onClick={() => handleSidebarClick('applications')}
424
+ className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === 'applications' ? 'bg-gray-200 text-black' : 'text-gray-600 hover:bg-gray-100'}`}
425
  >
426
+ <AppWindow size={18} weight="fill" className="text-green-500" />
427
+ Applications
428
  </button>
429
  </nav>
430
  </div>
 
525
  onNavigate(file.path)
526
  } else if (file.type === 'flutter_app' && onOpenFlutterApp) {
527
  onOpenFlutterApp(file)
528
+ } else if (file.extension === 'dart' || file.extension === 'flutter') {
529
+ // Open Flutter files in Flutter IDE
530
+ if (onOpenApp) {
531
+ onOpenApp('flutter-editor')
532
+ }
533
+ } else if (file.extension === 'tex') {
534
+ // Open LaTeX files in LaTeX Studio
535
+ if (onOpenApp) {
536
+ onOpenApp('latex-editor')
537
+ }
538
  } else {
539
+ // Preview other files
540
  handlePreview(file)
541
  }
542
  }}
app/components/Window.tsx CHANGED
@@ -20,6 +20,7 @@ interface WindowProps {
20
  resizable?: boolean;
21
  className?: string;
22
  headerClassName?: string;
 
23
  darkMode?: boolean;
24
  }
25
 
@@ -40,6 +41,7 @@ const Window: React.FC<WindowProps> = ({
40
  resizable = true,
41
  className = '',
42
  headerClassName = '',
 
43
  darkMode = false,
44
  }) => {
45
  const [isMaximized, setIsMaximized] = React.useState(false);
@@ -114,9 +116,11 @@ const Window: React.FC<WindowProps> = ({
114
 
115
  // Calculate Rnd props based on state
116
  const rndProps = isMaximized ? {
117
- position: { x: 0, y: 32 }, // 32px for TopBar offset (h-8 = 32px)
118
- size: { width: typeof window !== 'undefined' ? window.innerWidth : '100vw',
119
- height: typeof window !== 'undefined' ? window.innerHeight - 32 : 'calc(100vh - 32px)' },
 
 
120
  disableDragging: true,
121
  enableResizing: false
122
  } : {
@@ -146,9 +150,9 @@ const Window: React.FC<WindowProps> = ({
146
  }}
147
  onDrag={(e, d) => {
148
  // Constrain dragging to not go above TopBar
149
- if (d.y < 32) {
150
  // Update position ref to boundary
151
- currentPositionRef.current = { x: d.x, y: 32 };
152
  return false;
153
  }
154
  // Update position ref during drag
@@ -158,7 +162,7 @@ const Window: React.FC<WindowProps> = ({
158
  setIsDraggingOrResizing(false);
159
  if (!isMaximized) {
160
  // Ensure window doesn't go above TopBar and update position ref
161
- const constrainedY = Math.max(d.y, 32);
162
  currentPositionRef.current = { x: d.x, y: constrainedY };
163
  }
164
  }}
@@ -195,7 +199,7 @@ const Window: React.FC<WindowProps> = ({
195
  </div>
196
  <span className="font-semibold text-gray-700 flex-1 text-center pr-16 text-sm select-none">{title}</span>
197
  </div>
198
- <div className="flex-1 overflow-auto bg-white/90 backdrop-blur-sm" style={{ height: 'calc(100% - 2.5rem)' }}>
199
  {children}
200
  </div>
201
  </div>
 
20
  resizable?: boolean;
21
  className?: string;
22
  headerClassName?: string;
23
+ contentClassName?: string;
24
  darkMode?: boolean;
25
  }
26
 
 
41
  resizable = true,
42
  className = '',
43
  headerClassName = '',
44
+ contentClassName = '',
45
  darkMode = false,
46
  }) => {
47
  const [isMaximized, setIsMaximized] = React.useState(false);
 
116
 
117
  // Calculate Rnd props based on state
118
  const rndProps = isMaximized ? {
119
+ position: { x: 0, y: 4 }, // 32px for TopBar offset (h-8 = 32px)
120
+ size: {
121
+ width: typeof window !== 'undefined' ? window.innerWidth : '100vw',
122
+ height: typeof window !== 'undefined' ? window.innerHeight - 4 : 'calc(100vh - 4px)'
123
+ },
124
  disableDragging: true,
125
  enableResizing: false
126
  } : {
 
150
  }}
151
  onDrag={(e, d) => {
152
  // Constrain dragging to not go above TopBar
153
+ if (d.y < 4) {
154
  // Update position ref to boundary
155
+ currentPositionRef.current = { x: d.x, y: 4 };
156
  return false;
157
  }
158
  // Update position ref during drag
 
162
  setIsDraggingOrResizing(false);
163
  if (!isMaximized) {
164
  // Ensure window doesn't go above TopBar and update position ref
165
+ const constrainedY = Math.max(d.y, 4);
166
  currentPositionRef.current = { x: d.x, y: constrainedY };
167
  }
168
  }}
 
199
  </div>
200
  <span className="font-semibold text-gray-700 flex-1 text-center pr-16 text-sm select-none">{title}</span>
201
  </div>
202
+ <div className={`flex-1 overflow-auto ${darkMode ? 'bg-gray-900/95 text-white' : 'bg-white/90'} backdrop-blur-sm ${contentClassName || ''}`} style={{ height: 'calc(100% - 2.5rem)' }}>
203
  {children}
204
  </div>
205
  </div>
mcp-server.js CHANGED
@@ -293,6 +293,106 @@ class ReubenOSMCPServer {
293
  required: ['name'],
294
  },
295
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  ],
297
  }));
298
 
@@ -325,6 +425,14 @@ class ReubenOSMCPServer {
325
  return await this.updateFlutterApp(args);
326
  case 'delete_flutter_app':
327
  return await this.deleteFlutterApp(args);
 
 
 
 
 
 
 
 
328
  default:
329
  throw new Error(`Unknown tool: ${name}`);
330
  }
@@ -669,6 +777,117 @@ class ReubenOSMCPServer {
669
  };
670
  }
671
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  async run() {
673
  const transport = new StdioServerTransport();
674
  await this.server.connect(transport);
 
293
  required: ['name'],
294
  },
295
  },
296
+ {
297
+ name: 'create_latex_document',
298
+ description: 'Create a LaTeX document that can be edited in LaTeX Studio',
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {
302
+ sessionKey: {
303
+ type: 'string',
304
+ description: 'Session ID for authentication (auto-filled if session verified)',
305
+ },
306
+ fileName: {
307
+ type: 'string',
308
+ description: 'Name of the LaTeX file (without .tex extension)',
309
+ },
310
+ content: {
311
+ type: 'string',
312
+ description: 'LaTeX document content',
313
+ },
314
+ isPublic: {
315
+ type: 'boolean',
316
+ description: 'Save to public folder',
317
+ default: false,
318
+ },
319
+ },
320
+ required: ['fileName', 'content'],
321
+ },
322
+ },
323
+ {
324
+ name: 'get_latex_document',
325
+ description: 'Retrieve a LaTeX document content',
326
+ inputSchema: {
327
+ type: 'object',
328
+ properties: {
329
+ sessionKey: {
330
+ type: 'string',
331
+ description: 'Session ID for authentication (auto-filled if session verified)',
332
+ },
333
+ fileName: {
334
+ type: 'string',
335
+ description: 'Name of the LaTeX file',
336
+ },
337
+ isPublic: {
338
+ type: 'boolean',
339
+ description: 'File is in public folder',
340
+ default: false,
341
+ },
342
+ },
343
+ required: ['fileName'],
344
+ },
345
+ },
346
+ {
347
+ name: 'update_latex_document',
348
+ description: 'Update an existing LaTeX document',
349
+ inputSchema: {
350
+ type: 'object',
351
+ properties: {
352
+ sessionKey: {
353
+ type: 'string',
354
+ description: 'Session ID for authentication (auto-filled if session verified)',
355
+ },
356
+ fileName: {
357
+ type: 'string',
358
+ description: 'Name of the LaTeX file to update',
359
+ },
360
+ content: {
361
+ type: 'string',
362
+ description: 'New LaTeX content',
363
+ },
364
+ isPublic: {
365
+ type: 'boolean',
366
+ description: 'File is in public folder',
367
+ default: false,
368
+ },
369
+ },
370
+ required: ['fileName', 'content'],
371
+ },
372
+ },
373
+ {
374
+ name: 'compile_latex_to_pdf',
375
+ description: 'Compile a LaTeX document to PDF',
376
+ inputSchema: {
377
+ type: 'object',
378
+ properties: {
379
+ sessionKey: {
380
+ type: 'string',
381
+ description: 'Session ID for authentication (auto-filled if session verified)',
382
+ },
383
+ fileName: {
384
+ type: 'string',
385
+ description: 'Name of the LaTeX file to compile',
386
+ },
387
+ isPublic: {
388
+ type: 'boolean',
389
+ description: 'File is in public folder',
390
+ default: false,
391
+ },
392
+ },
393
+ required: ['fileName'],
394
+ },
395
+ },
396
  ],
397
  }));
398
 
 
425
  return await this.updateFlutterApp(args);
426
  case 'delete_flutter_app':
427
  return await this.deleteFlutterApp(args);
428
+ case 'create_latex_document':
429
+ return await this.createLatexDocument(args);
430
+ case 'get_latex_document':
431
+ return await this.getLatexDocument(args);
432
+ case 'update_latex_document':
433
+ return await this.updateLatexDocument(args);
434
+ case 'compile_latex_to_pdf':
435
+ return await this.compileLatexToPdf(args);
436
  default:
437
  throw new Error(`Unknown tool: ${name}`);
438
  }
 
777
  };
778
  }
779
 
780
+ async createLatexDocument(args) {
781
+ const response = await fetch(`${BASE_URL}/api/latex/save`, {
782
+ method: 'POST',
783
+ headers: {
784
+ 'Content-Type': 'application/json',
785
+ 'x-session-id': args.sessionKey || this.sessionId,
786
+ },
787
+ body: JSON.stringify({
788
+ fileName: args.fileName.endsWith('.tex') ? args.fileName : `${args.fileName}.tex`,
789
+ content: args.content,
790
+ isPublic: args.isPublic || false,
791
+ }),
792
+ });
793
+
794
+ const data = await response.json();
795
+ return {
796
+ content: [
797
+ {
798
+ type: 'text',
799
+ text: data.success
800
+ ? `βœ… LaTeX document created successfully!\n\nπŸ“„ Document: ${data.fileName}\nπŸ“‚ Location: ${args.isPublic ? 'Public' : 'Session'} folder\n\n🎯 Next Steps:\n1. Open ReubenOS File Manager\n2. Navigate to ${args.isPublic ? 'Public' : 'My Files'}\n3. Double-click "${data.fileName}" to open in LaTeX Studio\n4. Edit and compile your document\n\n✨ Your LaTeX document is ready!`
801
+ : `❌ Failed to create LaTeX document: ${data.error}`,
802
+ },
803
+ ],
804
+ };
805
+ }
806
+
807
+ async getLatexDocument(args) {
808
+ // First download the file to get its content
809
+ const url = `${BASE_URL}/api/sessions/download?file=${encodeURIComponent(args.fileName)}${
810
+ args.isPublic ? '&public=true' : ''
811
+ }`;
812
+ const headers = args.isPublic ? {} : { 'x-session-id': args.sessionKey || this.sessionId };
813
+
814
+ const response = await fetch(url, { headers });
815
+
816
+ if (response.ok) {
817
+ const content = await response.text();
818
+ return {
819
+ content: [
820
+ {
821
+ type: 'text',
822
+ text: `LaTeX Document: ${args.fileName}\n\n${content}`,
823
+ },
824
+ ],
825
+ };
826
+ } else {
827
+ return {
828
+ content: [
829
+ {
830
+ type: 'text',
831
+ text: `Failed to get LaTeX document: ${response.statusText}`,
832
+ },
833
+ ],
834
+ };
835
+ }
836
+ }
837
+
838
+ async updateLatexDocument(args) {
839
+ const response = await fetch(`${BASE_URL}/api/latex/save`, {
840
+ method: 'POST',
841
+ headers: {
842
+ 'Content-Type': 'application/json',
843
+ 'x-session-id': args.sessionKey || this.sessionId,
844
+ },
845
+ body: JSON.stringify({
846
+ fileName: args.fileName.endsWith('.tex') ? args.fileName : `${args.fileName}.tex`,
847
+ content: args.content,
848
+ isPublic: args.isPublic || false,
849
+ }),
850
+ });
851
+
852
+ const data = await response.json();
853
+ return {
854
+ content: [
855
+ {
856
+ type: 'text',
857
+ text: data.success
858
+ ? `βœ… LaTeX document "${args.fileName}" updated successfully!`
859
+ : `❌ Failed to update LaTeX document: ${data.error}`,
860
+ },
861
+ ],
862
+ };
863
+ }
864
+
865
+ async compileLatexToPdf(args) {
866
+ const response = await fetch(`${BASE_URL}/api/latex/compile`, {
867
+ method: 'POST',
868
+ headers: {
869
+ 'Content-Type': 'application/json',
870
+ 'x-session-id': args.sessionKey || this.sessionId,
871
+ },
872
+ body: JSON.stringify({
873
+ fileName: args.fileName.endsWith('.tex') ? args.fileName : `${args.fileName}.tex`,
874
+ isPublic: args.isPublic || false,
875
+ }),
876
+ });
877
+
878
+ const data = await response.json();
879
+ return {
880
+ content: [
881
+ {
882
+ type: 'text',
883
+ text: data.success
884
+ ? `βœ… LaTeX compiled to PDF successfully!\n\nπŸ“„ PDF File: ${data.pdfFileName}\nπŸ“‚ Location: ${args.isPublic ? 'Public' : 'Session'} folder\n\n✨ Your PDF is ready for download!`
885
+ : `❌ Failed to compile LaTeX: ${data.error}`,
886
+ },
887
+ ],
888
+ };
889
+ }
890
+
891
  async run() {
892
  const transport = new StdioServerTransport();
893
  await this.server.connect(transport);
public/background.svg ADDED