Upload folder using huggingface_hub
Browse files
client/public/rebuild-nonce.txt
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
Rebuild nonce: 2025-10-25T16:
|
|
|
|
| 1 |
+
Rebuild nonce: 2025-10-25T16:45:12Z
|
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -1937,19 +1937,23 @@ const TutorialTasks: React.FC = () => {
|
|
| 1937 |
</div>
|
| 1938 |
</div>
|
| 1939 |
) : (
|
| 1940 |
-
// Regular task layout - use grid for stability
|
| 1941 |
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-start">
|
| 1942 |
<div className="w-full flex justify-center">
|
| 1943 |
-
{task.imageUrl.startsWith('data:') ? (
|
| 1944 |
<div className="inline-block rounded-lg shadow-md overflow-hidden">
|
| 1945 |
<img src={task.imageUrl} alt={task.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ height: '200px', width: 'auto', objectFit: 'contain' }} onError={(e) => { console.error('Image failed to load:', e); (e.currentTarget as HTMLImageElement).style.display = 'none'; }} />
|
| 1946 |
</div>
|
| 1947 |
-
) : (
|
| 1948 |
<div className="inline-block rounded-lg shadow-md bg-green-500 text-white p-6 text-center">
|
| 1949 |
<div className="text-3xl mb-2">📷</div>
|
| 1950 |
<div className="font-semibold">Image Uploaded</div>
|
| 1951 |
<div className="text-sm opacity-75">{task.imageUrl}</div>
|
| 1952 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1953 |
)}
|
| 1954 |
</div>
|
| 1955 |
<div className="w-full">
|
|
@@ -1957,40 +1961,6 @@ const TutorialTasks: React.FC = () => {
|
|
| 1957 |
<h5 className="text-indigo-900 font-semibold mb-2">Source Text</h5>
|
| 1958 |
<div className="text-blue-800 leading-relaxed text-lg font-source-text whitespace-pre-wrap">{renderFormatted(task.content)}</div>
|
| 1959 |
</div>
|
| 1960 |
-
{localStorage.getItem('token') && (
|
| 1961 |
-
<div className="bg-white rounded-lg p-4 border border-gray-200">
|
| 1962 |
-
<h5 className="text-gray-900 font-semibold mb-2">Your Group's Translation</h5>
|
| 1963 |
-
<div className="mb-2">
|
| 1964 |
-
<label className="block text-xs font-medium text-gray-700 mb-1">Select Your Group</label>
|
| 1965 |
-
<select
|
| 1966 |
-
value={selectedGroups[task._id] || ''}
|
| 1967 |
-
onChange={(e) => setSelectedGroups({ ...selectedGroups, [task._id]: parseInt(e.target.value) })}
|
| 1968 |
-
className="w-40 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-xs"
|
| 1969 |
-
>
|
| 1970 |
-
<option value="">Choose...</option>
|
| 1971 |
-
{[1,2,3,4,5,6,7,8].map((g) => (<option key={g} value={g}>Group {g}</option>))}
|
| 1972 |
-
</select>
|
| 1973 |
-
</div>
|
| 1974 |
-
<div className="flex items-center justify-end space-x-2 mb-2">
|
| 1975 |
-
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '**')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">B</button>
|
| 1976 |
-
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '*')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded italic">I</button>
|
| 1977 |
-
<button onClick={() => applyLinkFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }))} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">Link</button>
|
| 1978 |
-
</div>
|
| 1979 |
-
<textarea
|
| 1980 |
-
id={`tutorial-translation-${task._id}`}
|
| 1981 |
-
value={translationText[task._id] || ''}
|
| 1982 |
-
onInput={(e) => setTranslationText({ ...translationText, [task._id]: (e.target as HTMLTextAreaElement).value })}
|
| 1983 |
-
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
| 1984 |
-
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 1985 |
-
style={{ height: '150px' }}
|
| 1986 |
-
rows={4}
|
| 1987 |
-
placeholder="Enter your group's translation here..."
|
| 1988 |
-
/>
|
| 1989 |
-
<div className="flex justify-end mt-2">
|
| 1990 |
-
<button onClick={() => handleSubmitTranslation(task._id)} disabled={submitting[task._id]} className="btn-primary disabled:bg-gray-400 text-white px-4 py-2 rounded-lg text-sm">{submitting[task._id] ? 'Submitting...' : 'Submit Translation'}</button>
|
| 1991 |
-
</div>
|
| 1992 |
-
</div>
|
| 1993 |
-
)}
|
| 1994 |
</div>
|
| 1995 |
</div>
|
| 1996 |
)
|
|
@@ -2090,6 +2060,85 @@ const TutorialTasks: React.FC = () => {
|
|
| 2090 |
</div>
|
| 2091 |
)}
|
| 2092 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2093 |
|
| 2094 |
{/* Show login message for visitors */}
|
| 2095 |
{!localStorage.getItem('token') && (
|
|
|
|
| 1937 |
</div>
|
| 1938 |
</div>
|
| 1939 |
) : (
|
| 1940 |
+
// Regular task layout - use grid for stability like image-only layout
|
| 1941 |
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-start">
|
| 1942 |
<div className="w-full flex justify-center">
|
| 1943 |
+
{task.imageUrl && task.imageUrl.startsWith('data:') ? (
|
| 1944 |
<div className="inline-block rounded-lg shadow-md overflow-hidden">
|
| 1945 |
<img src={task.imageUrl} alt={task.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ height: '200px', width: 'auto', objectFit: 'contain' }} onError={(e) => { console.error('Image failed to load:', e); (e.currentTarget as HTMLImageElement).style.display = 'none'; }} />
|
| 1946 |
</div>
|
| 1947 |
+
) : task.imageUrl ? (
|
| 1948 |
<div className="inline-block rounded-lg shadow-md bg-green-500 text-white p-6 text-center">
|
| 1949 |
<div className="text-3xl mb-2">📷</div>
|
| 1950 |
<div className="font-semibold">Image Uploaded</div>
|
| 1951 |
<div className="text-sm opacity-75">{task.imageUrl}</div>
|
| 1952 |
</div>
|
| 1953 |
+
) : (
|
| 1954 |
+
<div className="w-full h-48 bg-gray-100 rounded-lg flex items-center justify-center">
|
| 1955 |
+
<div className="text-gray-500">No image</div>
|
| 1956 |
+
</div>
|
| 1957 |
)}
|
| 1958 |
</div>
|
| 1959 |
<div className="w-full">
|
|
|
|
| 1961 |
<h5 className="text-indigo-900 font-semibold mb-2">Source Text</h5>
|
| 1962 |
<div className="text-blue-800 leading-relaxed text-lg font-source-text whitespace-pre-wrap">{renderFormatted(task.content)}</div>
|
| 1963 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1964 |
</div>
|
| 1965 |
</div>
|
| 1966 |
)
|
|
|
|
| 2060 |
</div>
|
| 2061 |
)}
|
| 2062 |
|
| 2063 |
+
{/* Translation Input (always show if user is logged in, but hide for image-only content) */}
|
| 2064 |
+
{localStorage.getItem('token') && task.content !== 'Image-based task' && (
|
| 2065 |
+
<div className="bg-white rounded-lg p-6 border border-gray-200 shadow-sm safari-stable-form">
|
| 2066 |
+
<div className="flex items-center space-x-3 mb-4">
|
| 2067 |
+
<div className="bg-gray-100 rounded-lg p-2">
|
| 2068 |
+
<DocumentTextIcon className="h-4 w-4 text-gray-600" />
|
| 2069 |
+
</div>
|
| 2070 |
+
<h4 className="text-gray-900 font-semibold text-lg">Group Translation</h4>
|
| 2071 |
+
</div>
|
| 2072 |
+
|
| 2073 |
+
{/* Group Selection */}
|
| 2074 |
+
<div className="mb-4">
|
| 2075 |
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 2076 |
+
Select Your Group *
|
| 2077 |
+
</label>
|
| 2078 |
+
<select
|
| 2079 |
+
value={selectedGroups[task._id] || ''}
|
| 2080 |
+
onChange={(e) => {
|
| 2081 |
+
const value = parseInt(e.target.value);
|
| 2082 |
+
setSelectedGroups({ ...selectedGroups, [task._id]: value });
|
| 2083 |
+
}}
|
| 2084 |
+
className="w-48 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-sm"
|
| 2085 |
+
required
|
| 2086 |
+
>
|
| 2087 |
+
<option value="">Choose your group...</option>
|
| 2088 |
+
{[1, 2, 3, 4, 5, 6, 7, 8].map((group) => (
|
| 2089 |
+
<option key={group} value={group}>
|
| 2090 |
+
Group {group}
|
| 2091 |
+
</option>
|
| 2092 |
+
))}
|
| 2093 |
+
</select>
|
| 2094 |
+
</div>
|
| 2095 |
+
|
| 2096 |
+
<div className="mb-4">
|
| 2097 |
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 2098 |
+
Your Group's Translation *
|
| 2099 |
+
</label>
|
| 2100 |
+
<div className="flex items-center justify-end space-x-2 mb-2">
|
| 2101 |
+
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '**')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">B</button>
|
| 2102 |
+
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '*')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded italic">I</button>
|
| 2103 |
+
<button onClick={() => applyLinkFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }))} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">Link</button>
|
| 2104 |
+
</div>
|
| 2105 |
+
<textarea
|
| 2106 |
+
id={`tutorial-translation-${task._id}`}
|
| 2107 |
+
value={translationText[task._id] || ''}
|
| 2108 |
+
onInput={(e) => {
|
| 2109 |
+
const value = (e.target as HTMLTextAreaElement).value;
|
| 2110 |
+
setTranslationText({ ...translationText, [task._id]: value });
|
| 2111 |
+
}}
|
| 2112 |
+
onChange={(e) => {
|
| 2113 |
+
const value = e.target.value;
|
| 2114 |
+
setTranslationText({ ...translationText, [task._id]: value });
|
| 2115 |
+
}}
|
| 2116 |
+
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 2117 |
+
style={{ height: '150px' }}
|
| 2118 |
+
rows={4}
|
| 2119 |
+
placeholder="Enter your group's translation here..."
|
| 2120 |
+
/>
|
| 2121 |
+
</div>
|
| 2122 |
+
|
| 2123 |
+
<button
|
| 2124 |
+
onClick={() => { (withPreservedScroll.current || ((fn)=>fn()))(() => {}); handleSubmitTranslation(task._id); }}
|
| 2125 |
+
disabled={submitting[task._id]}
|
| 2126 |
+
className="relative inline-flex items-center justify-center gap-2 px-4 py-2 rounded-2xl text-sm font-medium text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] bg-sky-600/70 disabled:bg-gray-400"
|
| 2127 |
+
>
|
| 2128 |
+
{submitting[task._id] ? (
|
| 2129 |
+
<>
|
| 2130 |
+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
| 2131 |
+
Submitting...
|
| 2132 |
+
</>
|
| 2133 |
+
) : (
|
| 2134 |
+
<>
|
| 2135 |
+
Submit Group Translation
|
| 2136 |
+
<ArrowRightIcon className="h-4 w-4 ml-2" />
|
| 2137 |
+
</>
|
| 2138 |
+
)}
|
| 2139 |
+
</button>
|
| 2140 |
+
</div>
|
| 2141 |
+
)}
|
| 2142 |
|
| 2143 |
{/* Show login message for visitors */}
|
| 2144 |
{!localStorage.getItem('token') && (
|