Spaces:
Running
Running
changing background
Browse files- app/api/download/route.ts +13 -1
- app/api/sessions/download/route.ts +29 -1
- app/components/Desktop.tsx +1 -1
- app/components/FileManager.tsx +41 -38
- app/components/Window.tsx +11 -7
- mcp-server.js +219 -0
- public/background.svg +0 -0
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('
|
| 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('
|
| 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('
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 === '
|
| 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('
|
| 404 |
-
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === '
|
| 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 |
-
<
|
| 421 |
-
|
| 422 |
</button>
|
| 423 |
<button
|
| 424 |
-
onClick={() => handleSidebarClick('
|
| 425 |
-
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === '
|
| 426 |
>
|
| 427 |
-
<
|
| 428 |
-
|
| 429 |
</button>
|
| 430 |
<button
|
| 431 |
-
onClick={() => handleSidebarClick('
|
| 432 |
-
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm ${sidebarSelection === '
|
| 433 |
>
|
| 434 |
-
<
|
| 435 |
-
|
| 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:
|
| 118 |
-
size: {
|
| 119 |
-
|
|
|
|
|
|
|
| 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 <
|
| 150 |
// Update position ref to boundary
|
| 151 |
-
currentPositionRef.current = { x: d.x, y:
|
| 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,
|
| 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=
|
| 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
|
|