mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-11 23:59:47 +00:00
Merge remote-tracking branch 'origin/feature/new-project-creation' into feature/new-project-creation
This commit is contained in:
@@ -251,7 +251,12 @@ router.post('/create-workspace', async (req, res) => {
|
|||||||
await cloneGitHubRepository(githubUrl, absolutePath, githubToken);
|
await cloneGitHubRepository(githubUrl, absolutePath, githubToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Clean up created directory on failure
|
// Clean up created directory on failure
|
||||||
|
try {
|
||||||
await fs.rm(absolutePath, { recursive: true, force: true });
|
await fs.rm(absolutePath, { recursive: true, force: true });
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.error('Failed to clean up directory after clone failure:', cleanupError);
|
||||||
|
// Continue to throw original error
|
||||||
|
}
|
||||||
throw new Error(`Failed to clone repository: ${error.message}`);
|
throw new Error(`Failed to clone repository: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,63 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
function ImageViewer({ file, onClose }) {
|
function ImageViewer({ file, onClose }) {
|
||||||
const imagePath = `/api/projects/${file.projectName}/files/content?path=${encodeURIComponent(file.path)}`;
|
const imagePath = `/api/projects/${file.projectName}/files/content?path=${encodeURIComponent(file.path)}`;
|
||||||
|
const [imageUrl, setImageUrl] = useState(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let objectUrl;
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const loadImage = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setImageUrl(null);
|
||||||
|
|
||||||
|
const token = localStorage.getItem('auth-token');
|
||||||
|
if (!token) {
|
||||||
|
setError('Missing authentication token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(imagePath, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed with status ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
objectUrl = URL.createObjectURL(blob);
|
||||||
|
setImageUrl(objectUrl);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === 'AbortError') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error('Error loading image:', err);
|
||||||
|
setError('Unable to load image');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [imagePath]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
@@ -23,22 +77,24 @@ function ImageViewer({ file, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 flex justify-center items-center bg-gray-50 dark:bg-gray-900 min-h-[400px]">
|
<div className="p-4 flex justify-center items-center bg-gray-50 dark:bg-gray-900 min-h-[400px]">
|
||||||
|
{loading && (
|
||||||
|
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||||
|
<p>Loading image…</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && imageUrl && (
|
||||||
<img
|
<img
|
||||||
src={imagePath}
|
src={imageUrl}
|
||||||
alt={file.name}
|
alt={file.name}
|
||||||
className="max-w-full max-h-[70vh] object-contain rounded-lg shadow-md"
|
className="max-w-full max-h-[70vh] object-contain rounded-lg shadow-md"
|
||||||
onError={(e) => {
|
|
||||||
e.target.style.display = 'none';
|
|
||||||
e.target.nextSibling.style.display = 'block';
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<div
|
)}
|
||||||
className="text-center text-gray-500 dark:text-gray-400"
|
{!loading && !imageUrl && (
|
||||||
style={{ display: 'none' }}
|
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||||
>
|
<p>{error || 'Unable to load image'}</p>
|
||||||
<p>Unable to load image</p>
|
<p className="text-sm mt-2 break-all">{file.path}</p>
|
||||||
<p className="text-sm mt-2">{file.path}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 border-t bg-gray-50 dark:bg-gray-800">
|
<div className="p-4 border-t bg-gray-50 dark:bg-gray-800">
|
||||||
|
|||||||
@@ -344,6 +344,48 @@ function Sidebar({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createNewProject = async () => {
|
||||||
|
if (!newProjectPath.trim()) {
|
||||||
|
alert('Please enter a project path');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCreatingProject(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.createProject(newProjectPath.trim());
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Save the path to recent paths before clearing
|
||||||
|
saveToRecentPaths(newProjectPath.trim());
|
||||||
|
|
||||||
|
setShowNewProject(false);
|
||||||
|
setNewProjectPath('');
|
||||||
|
|
||||||
|
// Refresh projects to show the new one
|
||||||
|
if (window.refreshProjects) {
|
||||||
|
window.refreshProjects();
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
alert(error.error || 'Failed to create project. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating project:', error);
|
||||||
|
alert('Error creating project. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setCreatingProject(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelNewProject = () => {
|
||||||
|
setShowNewProject(false);
|
||||||
|
setNewProjectPath('');
|
||||||
|
};
|
||||||
|
|
||||||
const loadMoreSessions = async (project) => {
|
const loadMoreSessions = async (project) => {
|
||||||
// Check if we can load more sessions
|
// Check if we can load more sessions
|
||||||
|
|||||||
Reference in New Issue
Block a user