Compare commits
No commits in common. "5c100a015dddb4f5891e7d13d79e2bcd7a0e3a00" and "3472f203b94abc5430007f319c376fdd53d3b31a" have entirely different histories.
5c100a015d
...
3472f203b9
17
.env
Normal file
17
.env
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
PORT_FRONTEND=5173
|
||||||
|
PORT_FRONTEND_HMR=3002
|
||||||
|
PORT_API=800
|
||||||
|
PORT_SUPABASE=8000
|
||||||
|
|
||||||
|
VITE_PORT_FRONTEND=5173
|
||||||
|
VITE_PORT_FRONTEND_HMR=5173
|
||||||
|
|
||||||
|
VITE_APP_NAME=Classroom Copilot
|
||||||
|
VITE_SUPER_ADMIN_EMAIL=admin@classroomcopilot.ai
|
||||||
|
VITE_DEV=true
|
||||||
|
VITE_FRONTEND_SITE_URL=https://app.classroomcopilot.ai
|
||||||
|
VITE_APP_HMR_URL=https://app.classroomcopilot.ai
|
||||||
|
VITE_SUPABASE_URL=https://supa.classroomcopilot.ai
|
||||||
|
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||||
|
VITE_API_URL=https://api.classroomcopilot.ai
|
||||||
|
VITE_API_BASE=https://api.classroomcopilot.ai
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,8 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
.env
|
.env
|
||||||
# Build output
|
|
||||||
dist/
|
|
||||||
|
|
||||||
# Lock files - clean install
|
|
||||||
package-lock.json
|
|
||||||
21
Dockerfile
21
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
FROM node:20 AS builder
|
FROM node:20 as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
# TODO: Remove this or review embedded variables
|
# TODO: Remove this or review embedded variables
|
||||||
@ -20,24 +20,19 @@ RUN echo 'server { \
|
|||||||
root /usr/share/nginx/html; \
|
root /usr/share/nginx/html; \
|
||||||
index index.html; \
|
index index.html; \
|
||||||
location / { \
|
location / { \
|
||||||
try_files $uri $uri/ /index.html; \
|
try_files $uri $uri/ /index.html; \
|
||||||
expires 30d; \
|
expires 30d; \
|
||||||
add_header Cache-Control "public, no-transform"; \
|
add_header Cache-Control "public, no-transform"; \
|
||||||
} \
|
} \
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { \
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { \
|
||||||
expires 30d; \
|
expires 30d; \
|
||||||
add_header Cache-Control "public, no-transform"; \
|
add_header Cache-Control "public, no-transform"; \
|
||||||
} \
|
|
||||||
location /searxng-api/ { \
|
|
||||||
proxy_pass https://search.kevlarai.com/; \
|
|
||||||
proxy_ssl_server_name on; \
|
|
||||||
proxy_set_header Host search.kevlarai.com; \
|
|
||||||
} \
|
} \
|
||||||
location ~ /\. { \
|
location ~ /\. { \
|
||||||
deny all; \
|
deny all; \
|
||||||
} \
|
} \
|
||||||
error_page 404 /index.html; \
|
error_page 404 /index.html; \
|
||||||
}' > /etc/nginx/conf.d/default.conf
|
}' > /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Set up permissions
|
# Set up permissions
|
||||||
RUN chown -R nginx:nginx /usr/share/nginx/html \
|
RUN chown -R nginx:nginx /usr/share/nginx/html \
|
||||||
|
|||||||
59
dist/.vite/manifest.json
vendored
Normal file
59
dist/.vite/manifest.json
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"_vendor-mui.js": {
|
||||||
|
"file": "assets/vendor-mui.js",
|
||||||
|
"name": "vendor-mui",
|
||||||
|
"imports": [
|
||||||
|
"_vendor-react.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_vendor-react.js": {
|
||||||
|
"file": "assets/vendor-react.js",
|
||||||
|
"name": "vendor-react"
|
||||||
|
},
|
||||||
|
"_vendor-tldraw.js": {
|
||||||
|
"file": "assets/vendor-tldraw.js",
|
||||||
|
"name": "vendor-tldraw",
|
||||||
|
"imports": [
|
||||||
|
"_vendor-react.js",
|
||||||
|
"_vendor-mui.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_vendor-utils.js": {
|
||||||
|
"file": "assets/vendor-utils.js",
|
||||||
|
"name": "vendor-utils",
|
||||||
|
"imports": [
|
||||||
|
"_vendor-react.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"index.html": {
|
||||||
|
"file": "assets/index-CmYeIoD0.js",
|
||||||
|
"name": "index",
|
||||||
|
"src": "index.html",
|
||||||
|
"isEntry": true,
|
||||||
|
"imports": [
|
||||||
|
"_vendor-mui.js",
|
||||||
|
"_vendor-react.js",
|
||||||
|
"_vendor-tldraw.js",
|
||||||
|
"_vendor-utils.js"
|
||||||
|
],
|
||||||
|
"dynamicImports": [
|
||||||
|
"node_modules/pdfjs-dist/build/pdf.mjs"
|
||||||
|
],
|
||||||
|
"css": [
|
||||||
|
"assets/index.css"
|
||||||
|
],
|
||||||
|
"assets": [
|
||||||
|
"assets/pdf.worker.min.mjs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/pdfjs-dist/build/pdf.mjs": {
|
||||||
|
"file": "assets/pdf.js",
|
||||||
|
"name": "pdf",
|
||||||
|
"src": "node_modules/pdfjs-dist/build/pdf.mjs",
|
||||||
|
"isDynamicEntry": true
|
||||||
|
},
|
||||||
|
"node_modules/pdfjs-dist/build/pdf.worker.min.mjs": {
|
||||||
|
"file": "assets/pdf.worker.min.mjs",
|
||||||
|
"src": "node_modules/pdfjs-dist/build/pdf.worker.min.mjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
68827
dist/assets/index-CmYeIoD0.js
vendored
Normal file
68827
dist/assets/index-CmYeIoD0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-CmYeIoD0.js.map
vendored
Normal file
1
dist/assets/index-CmYeIoD0.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
11178
dist/assets/index.css
vendored
Normal file
11178
dist/assets/index.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21232
dist/assets/pdf.js
vendored
Normal file
21232
dist/assets/pdf.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/assets/pdf.js.map
vendored
Normal file
1
dist/assets/pdf.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
21
dist/assets/pdf.worker.min.mjs
vendored
Normal file
21
dist/assets/pdf.worker.min.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
19850
dist/assets/vendor-mui.js
vendored
Normal file
19850
dist/assets/vendor-mui.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-mui.js.map
vendored
Normal file
1
dist/assets/vendor-mui.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1901
dist/assets/vendor-react.js
vendored
Normal file
1901
dist/assets/vendor-react.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-react.js.map
vendored
Normal file
1
dist/assets/vendor-react.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
68796
dist/assets/vendor-tldraw.js
vendored
Normal file
68796
dist/assets/vendor-tldraw.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/vendor-tldraw.js.map
vendored
Normal file
1
dist/assets/vendor-tldraw.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12264
dist/assets/vendor-utils.js
vendored
Normal file
12264
dist/assets/vendor-utils.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-utils.js.map
vendored
Normal file
1
dist/assets/vendor-utils.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12
dist/audioWorklet.js
vendored
Normal file
12
dist/audioWorklet.js
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
class AudioProcessor extends AudioWorkletProcessor {
|
||||||
|
process(inputs) {
|
||||||
|
const input = inputs[0];
|
||||||
|
if (input.length > 0) {
|
||||||
|
const audioData = input[0];
|
||||||
|
this.port.postMessage(audioData);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('audio-processor', AudioProcessor);
|
||||||
BIN
dist/favicon.ico
vendored
Normal file
BIN
dist/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
dist/icons/icon-192x192-maskable.png
vendored
Normal file
BIN
dist/icons/icon-192x192-maskable.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
dist/icons/icon-192x192.png
vendored
Normal file
BIN
dist/icons/icon-192x192.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
dist/icons/icon-512x512-maskable.png
vendored
Normal file
BIN
dist/icons/icon-512x512-maskable.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
BIN
dist/icons/icon-512x512.png
vendored
Normal file
BIN
dist/icons/icon-512x512.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
21
dist/icons/sticker-tool.svg
vendored
Normal file
21
dist/icons/sticker-tool.svg
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Sticker outline -->
|
||||||
|
<path d="M20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<!-- Peeling corner effect -->
|
||||||
|
<path d="M16 8C16 10.2091 14.2091 12 12 12"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<!-- Star decoration -->
|
||||||
|
<path d="M12 8L13 9L12 10L11 9L12 8Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 689 B |
18
dist/index.html
vendored
Normal file
18
dist/index.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Classroom Copilot</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
<script type="module" crossorigin src="/assets/index-CmYeIoD0.js"></script>
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/vendor-mui.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tldraw.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/vendor-utils.js">
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index.css">
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
dist/manifest.webmanifest
vendored
Normal file
1
dist/manifest.webmanifest
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"ClassroomCopilot","short_name":"CC","start_url":"/","display":"fullscreen","background_color":"#ffffff","theme_color":"#000000","lang":"en","scope":"/","icons":[{"src":"/icons/icon-192x192.png","sizes":"192x192","type":"image/png","purpose":"any"},{"src":"/icons/icon-512x512.png","sizes":"512x512","type":"image/png","purpose":"any"},{"src":"/icons/icon-192x192-maskable.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"/icons/icon-512x512-maskable.png","sizes":"512x512","type":"image/png","purpose":"maskable"}]}
|
||||||
70
dist/offline.html
vendored
Normal file
70
dist/offline.html
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Offline - Classroom Copilot</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #1a73e8;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.retry-button {
|
||||||
|
background-color: #1a73e8;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.retry-button:hover {
|
||||||
|
background-color: #1557b0;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="icon">📡</div>
|
||||||
|
<h1>You're Offline</h1>
|
||||||
|
<p>It looks like you've lost your internet connection. Don't worry - any work you've done has been saved locally.</p>
|
||||||
|
<p>Please check your connection and try again.</p>
|
||||||
|
<button class="retry-button" onclick="window.location.reload()">Try Again</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Check if we're back online
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
dist/registerSW.js
vendored
Normal file
1
dist/registerSW.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js', { scope: '/' })})}
|
||||||
3889
dist/sw.js
vendored
Normal file
3889
dist/sw.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/sw.js.map
vendored
Normal file
1
dist/sw.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
16525
package-lock.json
generated
Normal file
16525
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -57,6 +57,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.15.0",
|
"@eslint/js": "^9.15.0",
|
||||||
|
"@storybook/addon-actions": "^8.6.12",
|
||||||
|
"@storybook/addon-essentials": "^8.6.12",
|
||||||
|
"@storybook/addon-interactions": "^8.6.12",
|
||||||
|
"@storybook/addon-links": "^8.6.12",
|
||||||
|
"@storybook/addon-onboarding": "^8.6.12",
|
||||||
|
"@storybook/react": "^8.6.12",
|
||||||
|
"@storybook/react-vite": "^8.6.12",
|
||||||
|
"@storybook/testing-library": "^0.2.2",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
@ -82,9 +90,10 @@
|
|||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"react-refresh": "^0.14.2",
|
"react-refresh": "^0.14.2",
|
||||||
|
"storybook": "^8.6.12",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite-plugin-pwa": "^0.21.1",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^1.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,6 @@ import { DatabaseNameService } from '../services/graph/databaseNameService';
|
|||||||
import { provisionUser } from '../services/provisioningService';
|
import { provisionUser } from '../services/provisioningService';
|
||||||
import { storageService, StorageKeys } from '../services/auth/localStorageService';
|
import { storageService, StorageKeys } from '../services/auth/localStorageService';
|
||||||
|
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
||||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
||||||
|
|
||||||
export interface UserContextType {
|
export interface UserContextType {
|
||||||
user: CCUser | null;
|
user: CCUser | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -32,9 +29,9 @@ export const UserContext = createContext<UserContextType>({
|
|||||||
preferences: {},
|
preferences: {},
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
updateProfile: async () => { },
|
updateProfile: async () => {},
|
||||||
updatePreferences: async () => { },
|
updatePreferences: async () => {},
|
||||||
clearError: () => { }
|
clearError: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
@ -66,7 +63,7 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let userInfo: User | null = null; // Declare at function scope
|
let userInfo: User | null = null; // Declare at function scope
|
||||||
try {
|
try {
|
||||||
logger.debug('user-context', '🔄 Resolving user profile', {
|
logger.debug('user-context', '🔄 Resolving user profile', {
|
||||||
@ -113,30 +110,30 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
email: userInfo.email
|
email: userInfo.email
|
||||||
});
|
});
|
||||||
|
|
||||||
let profileRow: Record<string, unknown> | null = null;
|
let profileRow: Record<string, unknown> | null = null;
|
||||||
|
|
||||||
logger.debug('user-context', '🔧 Step 5: Querying profiles table...', {
|
logger.debug('user-context', '🔧 Step 5: Querying profiles table...', {
|
||||||
userId: userInfo.id
|
userId: userInfo.id
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set loading state when we start the actual database query
|
// Set loading state when we start the actual database query
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Query profiles table without timeout to see actual error
|
// Query profiles table without timeout to see actual error
|
||||||
logger.debug('user-context', '🔧 Step 5b: Starting profiles query...', {
|
logger.debug('user-context', '🔧 Step 5b: Starting profiles query...', {
|
||||||
userId: userInfo.id,
|
userId: userInfo.id,
|
||||||
clientType: 'authenticated'
|
clientType: 'authenticated'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try direct fetch instead of Supabase client to bypass hanging issue
|
// Try direct fetch instead of Supabase client to bypass hanging issue
|
||||||
logger.debug('user-context', '🔧 Step 5b1: About to make profiles query with direct fetch...', {
|
logger.debug('user-context', '🔧 Step 5b1: About to make profiles query with direct fetch...', {
|
||||||
userId: userInfo.id,
|
userId: userInfo.id,
|
||||||
queryStarted: true
|
queryStarted: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error } = await fetch(`${supabaseUrl}/rest/v1/profiles?select=*&id=eq.${userInfo.id}`, {
|
const { data, error } = await fetch(`http://localhost:8000/rest/v1/profiles?select=*&id=eq.${userInfo.id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${supabaseAnonKey}`,
|
'Authorization': `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -154,20 +151,20 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
});
|
});
|
||||||
return { data: null, error: { message: err.message, code: 'FETCH_ERROR' } };
|
return { data: null, error: { message: err.message, code: 'FETCH_ERROR' } };
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('user-context', '🔧 Step 5b2: Direct fetch completed...', {
|
logger.debug('user-context', '🔧 Step 5b2: Direct fetch completed...', {
|
||||||
userId: userInfo.id,
|
userId: userInfo.id,
|
||||||
hasData: !!data,
|
hasData: !!data,
|
||||||
hasError: !!error
|
hasError: !!error
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('user-context', '🔧 Step 5c: Profiles query completed', {
|
logger.debug('user-context', '🔧 Step 5c: Profiles query completed', {
|
||||||
hasData: !!data,
|
hasData: !!data,
|
||||||
hasError: !!error,
|
hasError: !!error,
|
||||||
errorCode: error?.code,
|
errorCode: error?.code,
|
||||||
errorMessage: error?.message
|
errorMessage: error?.message
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('user-context', '🔧 Step 5a: Profiles query result', {
|
logger.debug('user-context', '🔧 Step 5a: Profiles query result', {
|
||||||
hasData: !!data,
|
hasData: !!data,
|
||||||
hasError: !!error,
|
hasError: !!error,
|
||||||
@ -319,7 +316,7 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
userId: userInfo?.id,
|
userId: userInfo?.id,
|
||||||
email: userInfo?.email
|
email: userInfo?.email
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userInfo) {
|
if (userInfo) {
|
||||||
const metadata = userInfo.user_metadata as CCUserMetadata;
|
const metadata = userInfo.user_metadata as CCUserMetadata;
|
||||||
const fallbackProfile: CCUser = {
|
const fallbackProfile: CCUser = {
|
||||||
@ -333,12 +330,12 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
created_at: userInfo.created_at,
|
created_at: userInfo.created_at,
|
||||||
updated_at: userInfo.updated_at
|
updated_at: userInfo.updated_at
|
||||||
};
|
};
|
||||||
|
|
||||||
DatabaseNameService.rememberDatabaseNames({
|
DatabaseNameService.rememberDatabaseNames({
|
||||||
userDbName: fallbackProfile.user_db_name,
|
userDbName: fallbackProfile.user_db_name,
|
||||||
schoolDbName: fallbackProfile.school_db_name
|
schoolDbName: fallbackProfile.school_db_name
|
||||||
});
|
});
|
||||||
|
|
||||||
setProfile(fallbackProfile);
|
setProfile(fallbackProfile);
|
||||||
logger.debug('user-context', '✅ Fallback profile created', {
|
logger.debug('user-context', '✅ Fallback profile created', {
|
||||||
userId: fallbackProfile.id,
|
userId: fallbackProfile.id,
|
||||||
@ -348,7 +345,7 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
} else {
|
} else {
|
||||||
setProfile(null);
|
setProfile(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreferences({});
|
setPreferences({});
|
||||||
setError(error instanceof Error ? error : new Error('Failed to load user profile'));
|
setError(error instanceof Error ? error : new Error('Failed to load user profile'));
|
||||||
setLoading(false); // Ensure loading is cleared on error
|
setLoading(false); // Ensure loading is cleared on error
|
||||||
@ -356,12 +353,12 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
logger.debug('user-context', '🔧 Finalizing user context initialization...', {
|
logger.debug('user-context', '🔧 Finalizing user context initialization...', {
|
||||||
isMounted: mountedRef.current
|
isMounted: mountedRef.current
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mountedRef.current) {
|
if (mountedRef.current) {
|
||||||
// Loading state is already managed above, just log completion
|
// Loading state is already managed above, just log completion
|
||||||
logger.debug('user-context', '✅ User context initialization complete');
|
logger.debug('user-context', '✅ User context initialization complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('user-context', '🔧 Step 10: Setting isInitialized to true');
|
logger.debug('user-context', '🔧 Step 10: Setting isInitialized to true');
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
logger.debug('user-context', '✅ User context initialized flag set - initialization complete!', {
|
logger.debug('user-context', '✅ User context initialized flag set - initialization complete!', {
|
||||||
|
|||||||
@ -110,8 +110,7 @@ export type LogCategory =
|
|||||||
| 'auth-service'
|
| 'auth-service'
|
||||||
| 'user-context'
|
| 'user-context'
|
||||||
| 'neo-user-context'
|
| 'neo-user-context'
|
||||||
| 'neo-institute-context'
|
| 'neo-institute-context';
|
||||||
| 'search-service';
|
|
||||||
|
|
||||||
interface LogConfig {
|
interface LogConfig {
|
||||||
enabled: boolean; // Master switch to turn logging on/off
|
enabled: boolean; // Master switch to turn logging on/off
|
||||||
@ -339,7 +338,6 @@ logger.setConfig({
|
|||||||
'user-context',
|
'user-context',
|
||||||
'neo-user-context',
|
'neo-user-context',
|
||||||
'neo-institute-context',
|
'neo-institute-context',
|
||||||
'search-service'
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -42,10 +42,10 @@ import {
|
|||||||
Info as InfoIcon
|
Info as InfoIcon
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { supabase } from '../../supabaseClient';
|
import { supabase } from '../../supabaseClient';
|
||||||
import {
|
import {
|
||||||
pickDirectory,
|
pickDirectory,
|
||||||
processDirectoryFiles,
|
processDirectoryFiles,
|
||||||
calculateDirectoryStats,
|
calculateDirectoryStats,
|
||||||
formatFileSize,
|
formatFileSize,
|
||||||
isDirectoryPickerSupported,
|
isDirectoryPickerSupported,
|
||||||
FileWithPath
|
FileWithPath
|
||||||
@ -104,7 +104,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
const [files, setFiles] = useState<FileRecord[]>([]);
|
const [files, setFiles] = useState<FileRecord[]>([]);
|
||||||
const [pagination, setPagination] = useState<PaginationInfo | null>(null);
|
const [pagination, setPagination] = useState<PaginationInfo | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// Pagination and filtering state
|
// Pagination and filtering state
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||||
@ -128,7 +128,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
const apiFetch = useCallback(async (url: string, init?: { method?: string; body?: FormData | string; headers?: Record<string, string> }) => {
|
const apiFetch = useCallback(async (url: string, init?: { method?: string; body?: FormData | string; headers?: Record<string, string> }) => {
|
||||||
const session = await supabase.auth.getSession();
|
const session = await supabase.auth.getSession();
|
||||||
const token = session?.data?.session?.access_token;
|
const token = session?.data?.session?.access_token;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error('No authentication token available');
|
throw new Error('No authentication token available');
|
||||||
}
|
}
|
||||||
@ -140,12 +140,12 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
|
|
||||||
const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}`;
|
const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}`;
|
||||||
const res = await fetch(fullUrl, { ...init, headers });
|
const res = await fetch(fullUrl, { ...init, headers });
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errorText = await res.text();
|
const errorText = await res.text();
|
||||||
throw new Error(`HTTP ${res.status}: ${errorText}`);
|
throw new Error(`HTTP ${res.status}: ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json();
|
return res.json();
|
||||||
}, [API_BASE]);
|
}, [API_BASE]);
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
|
|
||||||
const loadFiles = useCallback(async (cabinetId: string, page: number = currentPage) => {
|
const loadFiles = useCallback(async (cabinetId: string, page: number = currentPage) => {
|
||||||
if (!cabinetId) return;
|
if (!cabinetId) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// Build query parameters for pagination, search, and sorting
|
// Build query parameters for pagination, search, and sorting
|
||||||
@ -183,20 +183,20 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
sort_order: sortOrder,
|
sort_order: sortOrder,
|
||||||
include_directories: 'true'
|
include_directories: 'true'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
params.append('search', searchTerm);
|
params.append('search', searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the new simple upload endpoint for listing files with pagination
|
// Use the new simple upload endpoint for listing files with pagination
|
||||||
const data: FileListResponse = await apiFetch(`/simple-upload/files?${params.toString()}`);
|
const data: FileListResponse = await apiFetch(`/simple-upload/files?${params.toString()}`);
|
||||||
|
|
||||||
setFiles(data.files || []);
|
setFiles(data.files || []);
|
||||||
setPagination(data.pagination);
|
setPagination(data.pagination);
|
||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `Loaded ${data.files?.length || 0} files (${data.pagination.total_count} total)`
|
text: `Loaded ${data.files?.length || 0} files (${data.pagination.total_count} total)`
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to load files:', error);
|
console.error('Failed to load files:', error);
|
||||||
@ -240,7 +240,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
// Single file upload
|
// Single file upload
|
||||||
const handleSingleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSingleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.files || !selectedCabinet) return;
|
if (!e.target.files || !selectedCabinet) return;
|
||||||
|
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('cabinet_id', selectedCabinet);
|
formData.append('cabinet_id', selectedCabinet);
|
||||||
@ -250,22 +250,22 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Choose endpoint based on upload type
|
// Choose endpoint based on upload type
|
||||||
const endpoint = uploadType === 'new' ? '/simple-upload/files/upload' : '/database/files/upload';
|
const endpoint = uploadType === 'new' ? '/simple-upload/files/upload' : '/database/files/upload';
|
||||||
|
|
||||||
const result = await apiFetch(endpoint, {
|
const result = await apiFetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Upload result:', result);
|
console.log('Upload result:', result);
|
||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `File uploaded successfully using ${uploadType === 'new' ? 'NEW' : 'OLD'} endpoint: ${file.name}`
|
text: `File uploaded successfully using ${uploadType === 'new' ? 'NEW' : 'OLD'} endpoint: ${file.name}`
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadFiles(selectedCabinet);
|
await loadFiles(selectedCabinet);
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -311,14 +311,14 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
|
|
||||||
setSelectedFiles(files);
|
setSelectedFiles(files);
|
||||||
setDirectoryStats(calculateDirectoryStats(files));
|
setDirectoryStats(calculateDirectoryStats(files));
|
||||||
|
|
||||||
const progress: UploadProgress[] = files.map(file => ({
|
const progress: UploadProgress[] = files.map(file => ({
|
||||||
path: file.relativePath,
|
path: file.relativePath,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
status: 'queued',
|
status: 'queued',
|
||||||
progress: 0
|
progress: 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setUploadProgress(progress);
|
setUploadProgress(progress);
|
||||||
setShowUploadDialog(true);
|
setShowUploadDialog(true);
|
||||||
};
|
};
|
||||||
@ -327,61 +327,61 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
if (!selectedCabinet || selectedFiles.length === 0) return;
|
if (!selectedCabinet || selectedFiles.length === 0) return;
|
||||||
|
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const firstFilePath = selectedFiles[0].relativePath;
|
const firstFilePath = selectedFiles[0].relativePath;
|
||||||
const directoryName = firstFilePath.split('/')[0] || 'uploaded-folder';
|
const directoryName = firstFilePath.split('/')[0] || 'uploaded-folder';
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('cabinet_id', selectedCabinet);
|
formData.append('cabinet_id', selectedCabinet);
|
||||||
formData.append('scope', 'teacher');
|
formData.append('scope', 'teacher');
|
||||||
formData.append('directory_name', directoryName);
|
formData.append('directory_name', directoryName);
|
||||||
|
|
||||||
selectedFiles.forEach(file => {
|
selectedFiles.forEach(file => {
|
||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
});
|
});
|
||||||
|
|
||||||
const relativePaths = selectedFiles.map(f => f.relativePath);
|
const relativePaths = selectedFiles.map(f => f.relativePath);
|
||||||
formData.append('file_paths', JSON.stringify(relativePaths));
|
formData.append('file_paths', JSON.stringify(relativePaths));
|
||||||
|
|
||||||
const result = await apiFetch('/simple-upload/files/upload-directory', {
|
const result = await apiFetch('/simple-upload/files/upload-directory', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Directory upload result:', result);
|
console.log('Directory upload result:', result);
|
||||||
|
|
||||||
setUploadProgress(prev => prev.map(item => ({
|
setUploadProgress(prev => prev.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
status: 'done',
|
status: 'done',
|
||||||
progress: 100
|
progress: 100
|
||||||
})));
|
})));
|
||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `Directory uploaded successfully: ${directoryName} (${selectedFiles.length} files)`
|
text: `Directory uploaded successfully: ${directoryName} (${selectedFiles.length} files)`
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadFiles(selectedCabinet);
|
await loadFiles(selectedCabinet);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowUploadDialog(false);
|
setShowUploadDialog(false);
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setUploadProgress([]);
|
setUploadProgress([]);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error('Directory upload failed:', error);
|
console.error('Directory upload failed:', error);
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
setMessage({ type: 'error', text: `Directory upload failed: ${errorMessage}` });
|
setMessage({ type: 'error', text: `Directory upload failed: ${errorMessage}` });
|
||||||
|
|
||||||
setUploadProgress(prev => prev.map(item => ({
|
setUploadProgress(prev => prev.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
status: 'error',
|
status: 'error',
|
||||||
error: String(error)
|
error: String(error)
|
||||||
})));
|
})));
|
||||||
|
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -404,12 +404,12 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('processing_type', 'basic');
|
formData.append('processing_type', 'basic');
|
||||||
|
|
||||||
const result = await apiFetch(`/simple-upload/files/${fileId}/process-manual`, {
|
const result = await apiFetch(`/simple-upload/files/${fileId}/process-manual`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Manual processing result:', result);
|
console.log('Manual processing result:', result);
|
||||||
setMessage({ type: 'info', text: 'Manual processing triggered (not yet implemented)' });
|
setMessage({ type: 'info', text: 'Manual processing triggered (not yet implemented)' });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -434,7 +434,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
🧪 Simple Upload Test Page
|
🧪 Simple Upload Test Page
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Alert severity="info" sx={{ mb: 3 }}>
|
<Alert severity="info" sx={{ mb: 3 }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
This page tests the NEW simple upload system (no auto-processing) vs the OLD system (with auto-processing).
|
This page tests the NEW simple upload system (no auto-processing) vs the OLD system (with auto-processing).
|
||||||
@ -452,8 +452,8 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{/* Upload Controls */}
|
{/* Upload Controls */}
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title="Upload Controls"
|
title="Upload Controls"
|
||||||
avatar={<UploadIcon />}
|
avatar={<UploadIcon />}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -504,15 +504,15 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
Upload File
|
Upload File
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref={dirInputRef}
|
ref={dirInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
{...({ webkitdirectory: '' } as any)}
|
{...({ webkitdirectory: '' } as any)}
|
||||||
multiple
|
multiple
|
||||||
onChange={handleFallbackDirectorySelect}
|
onChange={handleFallbackDirectorySelect}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<FolderOpenIcon />}
|
startIcon={<FolderOpenIcon />}
|
||||||
@ -552,8 +552,8 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{/* System Info */}
|
{/* System Info */}
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title="System Info"
|
title="System Info"
|
||||||
avatar={<InfoIcon />}
|
avatar={<InfoIcon />}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -594,8 +594,8 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
<strong>Upload Mode:</strong>
|
<strong>Upload Mode:</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={uploadType === 'new' ? 'NEW (Simple)' : 'OLD (Auto-Processing)'}
|
label={uploadType === 'new' ? 'NEW (Simple)' : 'OLD (Auto-Processing)'}
|
||||||
color={uploadType === 'new' ? 'success' : 'warning'}
|
color={uploadType === 'new' ? 'success' : 'warning'}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -629,7 +629,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{/* File List */}
|
{/* File List */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
@ -656,7 +656,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
sx={{ minWidth: 200 }}
|
sx={{ minWidth: 200 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||||
<InputLabel>Sort by</InputLabel>
|
<InputLabel>Sort by</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -670,7 +670,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<MenuItem value="processing_status">Status</MenuItem>
|
<MenuItem value="processing_status">Status</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 100 }}>
|
<FormControl size="small" sx={{ minWidth: 100 }}>
|
||||||
<InputLabel>Order</InputLabel>
|
<InputLabel>Order</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -682,7 +682,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<MenuItem value="desc">Descending</MenuItem>
|
<MenuItem value="desc">Descending</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 100 }}>
|
<FormControl size="small" sx={{ minWidth: 100 }}>
|
||||||
<InputLabel>Per page</InputLabel>
|
<InputLabel>Per page</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -702,12 +702,12 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* File List with Fixed Height */}
|
{/* File List with Fixed Height */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
height: 400, // Fixed height
|
height: 400, // Fixed height
|
||||||
overflow: 'auto'
|
overflow: 'auto'
|
||||||
}}>
|
}}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 2 }}>
|
||||||
@ -757,9 +757,9 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{file.name}
|
{file.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
{file.is_directory && <Chip label="Directory" size="small" />}
|
{file.is_directory && <Chip label="Directory" size="small" />}
|
||||||
<Chip
|
<Chip
|
||||||
label={file.processing_status || 'unknown'}
|
label={file.processing_status || 'unknown'}
|
||||||
size="small"
|
size="small"
|
||||||
color={getStatusColor(file.processing_status)}
|
color={getStatusColor(file.processing_status)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -789,7 +789,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{pagination && pagination.total_pages > 1 && (
|
{pagination && pagination.total_pages > 1 && (
|
||||||
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Stack spacing={2} alignItems="center">
|
<Stack spacing={2} alignItems="center">
|
||||||
<Pagination
|
<Pagination
|
||||||
count={pagination.total_pages}
|
count={pagination.total_pages}
|
||||||
page={pagination.page}
|
page={pagination.page}
|
||||||
onChange={(event, value) => setCurrentPage(value)}
|
onChange={(event, value) => setCurrentPage(value)}
|
||||||
@ -817,18 +817,18 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
{isUploading && <LinearProgress sx={{ flexGrow: 1, ml: 2 }} />}
|
{isUploading && <LinearProgress sx={{ flexGrow: 1, ml: 2 }} />}
|
||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{directoryStats && (
|
{directoryStats && (
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<strong>{directoryStats.fileCount} files</strong> in{' '}
|
<strong>{directoryStats.fileCount} files</strong> in{' '}
|
||||||
<strong>{directoryStats.directoryCount} folders</strong><br />
|
<strong>{directoryStats.directoryCount} folders</strong><br/>
|
||||||
Total size: <strong>{directoryStats.formattedSize}</strong>
|
Total size: <strong>{directoryStats.formattedSize}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Paper variant="outlined" sx={{ p: 2, maxHeight: 300, overflow: 'auto' }}>
|
<Paper variant="outlined" sx={{ p: 2, maxHeight: 300, overflow: 'auto' }}>
|
||||||
{uploadProgress.map((item, i) => (
|
{uploadProgress.map((item, i) => (
|
||||||
<Box key={i} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', py: 0.5 }}>
|
<Box key={i} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', py: 0.5 }}>
|
||||||
@ -838,31 +838,31 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<Typography variant="body2" sx={{ mr: 2, minWidth: 80 }}>
|
<Typography variant="body2" sx={{ mr: 2, minWidth: 80 }}>
|
||||||
{formatFileSize(item.size)}
|
{formatFileSize(item.size)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={item.status}
|
label={item.status}
|
||||||
size="small"
|
size="small"
|
||||||
color={
|
color={
|
||||||
item.status === 'done' ? 'success' :
|
item.status === 'done' ? 'success' :
|
||||||
item.status === 'error' ? 'error' :
|
item.status === 'error' ? 'error' :
|
||||||
item.status === 'uploading' ? 'primary' : 'default'
|
item.status === 'uploading' ? 'primary' : 'default'
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
item.status === 'done' ? <SuccessIcon /> :
|
item.status === 'done' ? <SuccessIcon /> :
|
||||||
item.status === 'error' ? <ErrorIcon /> : undefined
|
item.status === 'error' ? <ErrorIcon /> : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Paper>
|
</Paper>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setShowUploadDialog(false)} disabled={isUploading}>
|
<Button onClick={() => setShowUploadDialog(false)} disabled={isUploading}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={startDirectoryUpload}
|
onClick={startDirectoryUpload}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isUploading || selectedFiles.length === 0}
|
disabled={isUploading || selectedFiles.length === 0}
|
||||||
>
|
>
|
||||||
{isUploading ? 'Uploading...' : 'Start Upload'}
|
{isUploading ? 'Uploading...' : 'Start Upload'}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { HEADER_HEIGHT } from './Layout';
|
|||||||
|
|
||||||
const SearxngPage: React.FC = () => {
|
const SearxngPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: HEADER_HEIGHT,
|
top: HEADER_HEIGHT,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import logger from "../../debugConfig"
|
|
||||||
|
|
||||||
interface SearXNGResult {
|
interface SearXNGResult {
|
||||||
title?: string
|
title?: string
|
||||||
url?: string
|
url?: string
|
||||||
@ -28,25 +26,19 @@ export class SearchService {
|
|||||||
engines: 'google,bing,duckduckgo',
|
engines: 'google,bing,duckduckgo',
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `/searxng-api/search?${searchParams.toString()}`
|
const url = `${import.meta.env.VITE_FRONTEND_SITE_URL}/searxng-api/search?${searchParams.toString()}`
|
||||||
logger.debug('search-service', "Search URL: ", url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
logger.debug('search-service', "Search response: ", response)
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Search failed with status: ${response.status}`)
|
throw new Error(`Search failed with status: ${response.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
logger.debug('search-service', "Search data: ", data)
|
|
||||||
|
|
||||||
return (data.results || []).map((result: SearXNGResult) => ({
|
return (data.results || []).map((result: SearXNGResult) => ({
|
||||||
title: result.title || '',
|
title: result.title || '',
|
||||||
url: result.url || '',
|
url: result.url || '',
|
||||||
|
|||||||
36
src/sw.ts
36
src/sw.ts
@ -40,13 +40,13 @@ const manifestIndexEntry = manifest.find(entry => {
|
|||||||
|
|
||||||
// Create index entries using the revision from manifest if available
|
// Create index entries using the revision from manifest if available
|
||||||
const indexEntries = [
|
const indexEntries = [
|
||||||
{
|
{
|
||||||
url: '/index.html',
|
url: '/index.html',
|
||||||
revision: manifestIndexEntry && typeof manifestIndexEntry !== 'string' ? manifestIndexEntry.revision : null
|
revision: manifestIndexEntry && typeof manifestIndexEntry !== 'string' ? manifestIndexEntry.revision : null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/',
|
url: '/',
|
||||||
revision: manifestIndexEntry && typeof manifestIndexEntry !== 'string' ? manifestIndexEntry.revision : null
|
revision: manifestIndexEntry && typeof manifestIndexEntry !== 'string' ? manifestIndexEntry.revision : null
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -92,12 +92,12 @@ const navigationHandler = async ({ request }: { request: Request }): Promise<Res
|
|||||||
|
|
||||||
// If network fails, try cache
|
// If network fails, try cache
|
||||||
const cache = await caches.open(CACHE_NAMES.pages);
|
const cache = await caches.open(CACHE_NAMES.pages);
|
||||||
|
|
||||||
// Try both root and /index.html paths
|
// Try both root and /index.html paths
|
||||||
const cachedResponse = await cache.match(request) ||
|
const cachedResponse = await cache.match(request) ||
|
||||||
await cache.match('/') ||
|
await cache.match('/') ||
|
||||||
await cache.match('/index.html');
|
await cache.match('/index.html');
|
||||||
|
|
||||||
if (cachedResponse) {
|
if (cachedResponse) {
|
||||||
return cachedResponse;
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
@ -151,8 +151,8 @@ registerRoute(
|
|||||||
|
|
||||||
// Cache manifest and icons with a Stale While Revalidate strategy
|
// Cache manifest and icons with a Stale While Revalidate strategy
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({ request }) =>
|
({ request }) =>
|
||||||
request.destination === 'manifest' ||
|
request.destination === 'manifest' ||
|
||||||
request.url.includes('/icons/'),
|
request.url.includes('/icons/'),
|
||||||
new StaleWhileRevalidate({
|
new StaleWhileRevalidate({
|
||||||
cacheName: 'manifest-and-icons',
|
cacheName: 'manifest-and-icons',
|
||||||
@ -168,10 +168,10 @@ registerRoute(
|
|||||||
registerRoute(
|
registerRoute(
|
||||||
({ request }) => {
|
({ request }) => {
|
||||||
const destination = request.destination;
|
const destination = request.destination;
|
||||||
return destination === 'style' ||
|
return destination === 'style' ||
|
||||||
destination === 'script' ||
|
destination === 'script' ||
|
||||||
destination === 'image' ||
|
destination === 'image' ||
|
||||||
destination === 'font'
|
destination === 'font'
|
||||||
},
|
},
|
||||||
new CacheFirst({
|
new CacheFirst({
|
||||||
cacheName: CACHE_NAMES.static,
|
cacheName: CACHE_NAMES.static,
|
||||||
@ -209,7 +209,7 @@ registerRoute(
|
|||||||
|
|
||||||
// Cache SearXNG API with Network First strategy
|
// Cache SearXNG API with Network First strategy
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({ url }) => url.pathname.startsWith('/searxng-api/'),
|
({ url }) => url.pathname.startsWith('/searxng-api'),
|
||||||
new NetworkFirst({
|
new NetworkFirst({
|
||||||
cacheName: CACHE_NAMES.api,
|
cacheName: CACHE_NAMES.api,
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -227,7 +227,7 @@ registerRoute(
|
|||||||
|
|
||||||
// Cache SearXNG static assets
|
// Cache SearXNG static assets
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({ url }) => url.pathname.startsWith('/searxng-api/static/'),
|
({ url }) => url.pathname.startsWith('/searxng-api/static'),
|
||||||
new CacheFirst({
|
new CacheFirst({
|
||||||
cacheName: CACHE_NAMES.static,
|
cacheName: CACHE_NAMES.static,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export const CCCabinetsPanel: React.FC = () => {
|
|||||||
return createTheme({ palette: { mode, divider: 'var(--color-divider)' } });
|
return createTheme({ palette: { mode, divider: 'var(--color-divider)' } });
|
||||||
}, [tldrawPreferences?.colorScheme, prefersDarkMode]);
|
}, [tldrawPreferences?.colorScheme, prefersDarkMode]);
|
||||||
|
|
||||||
const API_BASE: string = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
const API_BASE: string = (import.meta as unknown as { env?: { VITE_API_BASE?: string } })?.env?.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
||||||
|
|
||||||
type RequestInitLite = { method?: string; body?: string | FormData | Blob | null; headers?: Record<string, string> } | undefined;
|
type RequestInitLite = { method?: string; body?: string | FormData | Blob | null; headers?: Record<string, string> } | undefined;
|
||||||
const apiFetch = async (url: string, init?: RequestInitLite) => {
|
const apiFetch = async (url: string, init?: RequestInitLite) => {
|
||||||
@ -75,7 +75,7 @@ export const CCCabinetsPanel: React.FC = () => {
|
|||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Box sx={{ p: 1, height: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ p: 1, height: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Button size="small" variant="outlined" startIcon={<AddIcon />} onClick={() => { setNewName(''); setCreateOpen(true); }}>New Cabinet</Button>
|
<Button size="small" variant="outlined" startIcon={<AddIcon/>} onClick={() => { setNewName(''); setCreateOpen(true); }}>New Cabinet</Button>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Grid container spacing={1} sx={{ overflow: 'auto' }}>
|
<Grid container spacing={1} sx={{ overflow: 'auto' }}>
|
||||||
{cabinets.map(c => (
|
{cabinets.map(c => (
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
createTheme,
|
createTheme,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
Button,
|
Button,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
IconButton,
|
IconButton,
|
||||||
styled,
|
styled,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Divider,
|
Divider,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@ -42,8 +42,8 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
|||||||
import { useTLDraw } from '../../../../../contexts/TLDrawContext';
|
import { useTLDraw } from '../../../../../contexts/TLDrawContext';
|
||||||
import { supabase } from '../../../../../supabaseClient';
|
import { supabase } from '../../../../../supabaseClient';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
calculateDirectoryStats,
|
calculateDirectoryStats,
|
||||||
isDirectoryPickerSupported,
|
isDirectoryPickerSupported,
|
||||||
FileWithPath
|
FileWithPath
|
||||||
} from '../../../../../utils/folderPicker';
|
} from '../../../../../utils/folderPicker';
|
||||||
@ -57,15 +57,15 @@ const Container = styled('div')(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
type Cabinet = { id: string; name: string };
|
type Cabinet = { id: string; name: string };
|
||||||
type FileRow = {
|
type FileRow = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
mime_type?: string;
|
mime_type?: string;
|
||||||
is_directory?: boolean;
|
is_directory?: boolean;
|
||||||
size_bytes?: number;
|
size_bytes?: number;
|
||||||
processing_status?: string;
|
processing_status?: string;
|
||||||
relative_path?: string;
|
relative_path?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
};
|
};
|
||||||
type Artefact = { id: string; type: string; rel_path: string; created_at: string };
|
type Artefact = { id: string; type: string; rel_path: string; created_at: string };
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [menuAnchor, setMenuAnchor] = useState<null | { el: HTMLElement; fileId: string }>(null);
|
const [menuAnchor, setMenuAnchor] = useState<null | { el: HTMLElement; fileId: string }>(null);
|
||||||
const [artefacts, setArtefacts] = useState<Artefact[]>([]);
|
const [artefacts, setArtefacts] = useState<Artefact[]>([]);
|
||||||
|
|
||||||
// Pagination and filtering state
|
// Pagination and filtering state
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(15); // Slightly more for main panel
|
const [itemsPerPage, setItemsPerPage] = useState(15); // Slightly more for main panel
|
||||||
@ -109,13 +109,13 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
const [sortBy, setSortBy] = useState('created_at');
|
const [sortBy, setSortBy] = useState('created_at');
|
||||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||||
const previousSearchTerm = useRef(searchTerm);
|
const previousSearchTerm = useRef(searchTerm);
|
||||||
|
|
||||||
// Directory navigation state
|
// Directory navigation state
|
||||||
const [currentDirectoryId, setCurrentDirectoryId] = useState<string | null>(null);
|
const [currentDirectoryId, setCurrentDirectoryId] = useState<string | null>(null);
|
||||||
const [breadcrumbs, setBreadcrumbs] = useState<{ id: string | null; name: string }[]>([
|
const [breadcrumbs, setBreadcrumbs] = useState<{ id: string | null; name: string }[]>([
|
||||||
{ id: null, name: 'Root' }
|
{ id: null, name: 'Root' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Directory upload state
|
// Directory upload state
|
||||||
const [selectedFiles, setSelectedFiles] = useState<FileWithPath[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<FileWithPath[]>([]);
|
||||||
const [showDirectoryDialog, setShowDirectoryDialog] = useState(false);
|
const [showDirectoryDialog, setShowDirectoryDialog] = useState(false);
|
||||||
@ -126,7 +126,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
totalSize: number;
|
totalSize: number;
|
||||||
formattedSize: string;
|
formattedSize: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
@ -139,7 +139,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
type RequestInitLike = { method?: string; body?: FormData | string | Blob | null; headers?: Record<string, string> } | undefined;
|
type RequestInitLike = { method?: string; body?: FormData | string | Blob | null; headers?: Record<string, string> } | undefined;
|
||||||
type HeadersInitLike = Record<string, string>;
|
type HeadersInitLike = Record<string, string>;
|
||||||
|
|
||||||
const API_BASE: string = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
const API_BASE: string = (import.meta as unknown as { env?: { VITE_API_BASE?: string } })?.env?.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
||||||
|
|
||||||
const apiFetch = useCallback(async (url: string, init?: RequestInitLike) => {
|
const apiFetch = useCallback(async (url: string, init?: RequestInitLike) => {
|
||||||
const headers: HeadersInitLike = {
|
const headers: HeadersInitLike = {
|
||||||
@ -168,7 +168,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
|
|
||||||
const loadFiles = useCallback(async (cabinetId: string, page: number = currentPage) => {
|
const loadFiles = useCallback(async (cabinetId: string, page: number = currentPage) => {
|
||||||
if (!cabinetId) return;
|
if (!cabinetId) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// Build query parameters for pagination, search, and sorting
|
// Build query parameters for pagination, search, and sorting
|
||||||
@ -180,19 +180,19 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
sort_order: sortOrder,
|
sort_order: sortOrder,
|
||||||
include_directories: 'true'
|
include_directories: 'true'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add directory filtering
|
// Add directory filtering
|
||||||
if (currentDirectoryId) {
|
if (currentDirectoryId) {
|
||||||
params.append('parent_directory_id', currentDirectoryId);
|
params.append('parent_directory_id', currentDirectoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
params.append('search', searchTerm);
|
params.append('search', searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the new simple upload endpoint for listing files with pagination
|
// Use the new simple upload endpoint for listing files with pagination
|
||||||
const data: FileListResponse = await apiFetch(`/simple-upload/files?${params.toString()}`);
|
const data: FileListResponse = await apiFetch(`/simple-upload/files?${params.toString()}`);
|
||||||
|
|
||||||
setFiles(data.files || []);
|
setFiles(data.files || []);
|
||||||
setPagination(data.pagination);
|
setPagination(data.pagination);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -226,7 +226,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedCabinet && searchTerm !== previousSearchTerm.current) {
|
if (selectedCabinet && searchTerm !== previousSearchTerm.current) {
|
||||||
previousSearchTerm.current = searchTerm;
|
previousSearchTerm.current = searchTerm;
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
setCurrentPage(1); // Reset to first page when searching
|
setCurrentPage(1); // Reset to first page when searching
|
||||||
loadFiles(selectedCabinet, 1);
|
loadFiles(selectedCabinet, 1);
|
||||||
@ -239,10 +239,10 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
// Directory navigation handlers
|
// Directory navigation handlers
|
||||||
const navigateToFolder = useCallback((folder: FileRow) => {
|
const navigateToFolder = useCallback((folder: FileRow) => {
|
||||||
if (!folder.is_directory) return;
|
if (!folder.is_directory) return;
|
||||||
|
|
||||||
setCurrentDirectoryId(folder.id);
|
setCurrentDirectoryId(folder.id);
|
||||||
setCurrentPage(1); // Reset to first page when entering folder
|
setCurrentPage(1); // Reset to first page when entering folder
|
||||||
|
|
||||||
// Add to breadcrumbs
|
// Add to breadcrumbs
|
||||||
setBreadcrumbs(prev => [...prev, { id: folder.id, name: folder.name }]);
|
setBreadcrumbs(prev => [...prev, { id: folder.id, name: folder.name }]);
|
||||||
}, []);
|
}, []);
|
||||||
@ -250,7 +250,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
const navigateToBreadcrumb = useCallback((targetBreadcrumb: { id: string | null; name: string }) => {
|
const navigateToBreadcrumb = useCallback((targetBreadcrumb: { id: string | null; name: string }) => {
|
||||||
setCurrentDirectoryId(targetBreadcrumb.id);
|
setCurrentDirectoryId(targetBreadcrumb.id);
|
||||||
setCurrentPage(1); // Reset to first page
|
setCurrentPage(1); // Reset to first page
|
||||||
|
|
||||||
// Trim breadcrumbs to the selected one
|
// Trim breadcrumbs to the selected one
|
||||||
setBreadcrumbs(prev => {
|
setBreadcrumbs(prev => {
|
||||||
const targetIndex = prev.findIndex(b => b.id === targetBreadcrumb.id && b.name === targetBreadcrumb.name);
|
const targetIndex = prev.findIndex(b => b.id === targetBreadcrumb.id && b.name === targetBreadcrumb.name);
|
||||||
@ -264,7 +264,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
// Directories come first
|
// Directories come first
|
||||||
if (a.is_directory && !b.is_directory) return -1;
|
if (a.is_directory && !b.is_directory) return -1;
|
||||||
if (!a.is_directory && b.is_directory) return 1;
|
if (!a.is_directory && b.is_directory) return 1;
|
||||||
|
|
||||||
// Within the same type (both directories or both files), sort alphabetically by name
|
// Within the same type (both directories or both files), sort alphabetically by name
|
||||||
return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
|
return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
});
|
});
|
||||||
@ -291,7 +291,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
|
|
||||||
const handleDirectorySelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleDirectorySelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.files || !selectedCabinet) return;
|
if (!e.target.files || !selectedCabinet) return;
|
||||||
|
|
||||||
// Convert FileList to FileWithPath array with relative paths
|
// Convert FileList to FileWithPath array with relative paths
|
||||||
const files: FileWithPath[] = [];
|
const files: FileWithPath[] = [];
|
||||||
Array.from(e.target.files).forEach(file => {
|
Array.from(e.target.files).forEach(file => {
|
||||||
@ -299,11 +299,11 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
(file as FileWithPath).relativePath = relativePath;
|
(file as FileWithPath).relativePath = relativePath;
|
||||||
files.push(file as FileWithPath);
|
files.push(file as FileWithPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
prepareDirectoryUpload(files);
|
prepareDirectoryUpload(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
(e.target as HTMLInputElement).value = '';
|
(e.target as HTMLInputElement).value = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -331,30 +331,30 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
if (!selectedCabinet || selectedFiles.length === 0) return;
|
if (!selectedCabinet || selectedFiles.length === 0) return;
|
||||||
|
|
||||||
setIsDirectoryUploading(true);
|
setIsDirectoryUploading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const firstFilePath = selectedFiles[0].relativePath;
|
const firstFilePath = selectedFiles[0].relativePath;
|
||||||
const directoryName = firstFilePath.split('/')[0] || 'uploaded-folder';
|
const directoryName = firstFilePath.split('/')[0] || 'uploaded-folder';
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('cabinet_id', selectedCabinet);
|
formData.append('cabinet_id', selectedCabinet);
|
||||||
formData.append('scope', 'teacher');
|
formData.append('scope', 'teacher');
|
||||||
formData.append('directory_name', directoryName);
|
formData.append('directory_name', directoryName);
|
||||||
|
|
||||||
selectedFiles.forEach(file => {
|
selectedFiles.forEach(file => {
|
||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
});
|
});
|
||||||
|
|
||||||
const relativePaths = selectedFiles.map(f => f.relativePath);
|
const relativePaths = selectedFiles.map(f => f.relativePath);
|
||||||
formData.append('file_paths', JSON.stringify(relativePaths));
|
formData.append('file_paths', JSON.stringify(relativePaths));
|
||||||
|
|
||||||
await apiFetch('/simple-upload/files/upload-directory', {
|
await apiFetch('/simple-upload/files/upload-directory', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadFiles(selectedCabinet);
|
await loadFiles(selectedCabinet);
|
||||||
|
|
||||||
setShowDirectoryDialog(false);
|
setShowDirectoryDialog(false);
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setDirectoryStats(null);
|
setDirectoryStats(null);
|
||||||
@ -387,10 +387,10 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
|
|
||||||
const iconForMime = (mime?: string, isDirectory?: boolean) => {
|
const iconForMime = (mime?: string, isDirectory?: boolean) => {
|
||||||
if (isDirectory) return <FolderIcon />;
|
if (isDirectory) return <FolderIcon />;
|
||||||
if (!mime) return <InsertDriveFileIcon />;
|
if (!mime) return <InsertDriveFileIcon/>;
|
||||||
if (mime.startsWith('image/')) return <ImageIcon />;
|
if (mime.startsWith('image/')) return <ImageIcon/>;
|
||||||
if (mime === 'application/pdf' || mime.startsWith('application/')) return <DescriptionIcon />;
|
if (mime === 'application/pdf' || mime.startsWith('application/')) return <DescriptionIcon/>;
|
||||||
return <InsertDriveFileIcon />;
|
return <InsertDriveFileIcon/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatFileSize = (bytes: number): string => {
|
const formatFileSize = (bytes: number): string => {
|
||||||
@ -436,10 +436,10 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
@ -486,7 +486,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
<MenuItem value="size_bytes">Size</MenuItem>
|
<MenuItem value="size_bytes">Size</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 60 }}>
|
<FormControl size="small" sx={{ minWidth: 60 }}>
|
||||||
<InputLabel>Order</InputLabel>
|
<InputLabel>Order</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -498,7 +498,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
<MenuItem value="desc">↓</MenuItem>
|
<MenuItem value="desc">↓</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 60 }}>
|
<FormControl size="small" sx={{ minWidth: 60 }}>
|
||||||
<InputLabel>Per page</InputLabel>
|
<InputLabel>Per page</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -517,10 +517,10 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Breadcrumb Navigation */}
|
{/* Breadcrumb Navigation */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
py: 1,
|
py: 1,
|
||||||
px: 1,
|
px: 1,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
@ -537,8 +537,8 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={() => navigateToBreadcrumb(breadcrumb)}
|
onClick={() => navigateToBreadcrumb(breadcrumb)}
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: 'auto',
|
minWidth: 'auto',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
color: index === breadcrumbs.length - 1 ? 'primary.main' : 'text.secondary',
|
color: index === breadcrumbs.length - 1 ? 'primary.main' : 'text.secondary',
|
||||||
fontWeight: index === breadcrumbs.length - 1 ? 600 : 400
|
fontWeight: index === breadcrumbs.length - 1 ? 600 : 400
|
||||||
@ -551,9 +551,9 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* File List with Fixed Height */}
|
{/* File List with Fixed Height */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
border: '1px solid var(--color-divider)',
|
border: '1px solid var(--color-divider)',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
height: 300, // Fixed height for main panel
|
height: 300, // Fixed height for main panel
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -565,7 +565,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box sx={{ p: 2, textAlign: 'center' }}>
|
<Box sx={{ p: 2, textAlign: 'center' }}>
|
||||||
<CircularProgress size={20} />
|
<CircularProgress size={20}/>
|
||||||
<Typography variant="caption" display="block" sx={{ mt: 1 }}>
|
<Typography variant="caption" display="block" sx={{ mt: 1 }}>
|
||||||
Loading files...
|
Loading files...
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -593,17 +593,17 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
secondaryAction={
|
secondaryAction={
|
||||||
<>
|
<>
|
||||||
<IconButton size="small" onClick={(e) => openMenu(e.currentTarget, f.id)} title="File actions">
|
<IconButton size="small" onClick={(e) => openMenu(e.currentTarget, f.id)} title="File actions">
|
||||||
<MoreVertIcon />
|
<MoreVertIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton edge="end" size="small" onClick={() => handleDelete(f.id)} title="Delete file">
|
<IconButton edge="end" size="small" onClick={() => handleDelete(f.id)} title="Delete file">
|
||||||
<DeleteIcon />
|
<DeleteIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{iconForMime(f.mime_type, f.is_directory)}
|
{iconForMime(f.mime_type, f.is_directory)}
|
||||||
<ListItemText
|
<ListItemText
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
|
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
|
||||||
@ -611,9 +611,9 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
{f.is_directory && <Chip label="Dir" size="small" />}
|
{f.is_directory && <Chip label="Dir" size="small" />}
|
||||||
{f.processing_status && f.processing_status !== 'uploaded' && (
|
{f.processing_status && f.processing_status !== 'uploaded' && (
|
||||||
<Chip
|
<Chip
|
||||||
label={f.processing_status}
|
label={f.processing_status}
|
||||||
size="small"
|
size="small"
|
||||||
color={getStatusColor(f.processing_status)}
|
color={getStatusColor(f.processing_status)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -632,17 +632,17 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
secondaryAction={
|
secondaryAction={
|
||||||
<>
|
<>
|
||||||
<IconButton size="small" onClick={(e) => openMenu(e.currentTarget, f.id)} title="File actions">
|
<IconButton size="small" onClick={(e) => openMenu(e.currentTarget, f.id)} title="File actions">
|
||||||
<MoreVertIcon />
|
<MoreVertIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton edge="end" size="small" onClick={() => handleDelete(f.id)} title="Delete file">
|
<IconButton edge="end" size="small" onClick={() => handleDelete(f.id)} title="Delete file">
|
||||||
<DeleteIcon />
|
<DeleteIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{iconForMime(f.mime_type, f.is_directory)}
|
{iconForMime(f.mime_type, f.is_directory)}
|
||||||
<ListItemText
|
<ListItemText
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
|
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
|
||||||
@ -650,9 +650,9 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
{f.is_directory && <Chip label="Dir" size="small" />}
|
{f.is_directory && <Chip label="Dir" size="small" />}
|
||||||
{f.processing_status && f.processing_status !== 'uploaded' && (
|
{f.processing_status && f.processing_status !== 'uploaded' && (
|
||||||
<Chip
|
<Chip
|
||||||
label={f.processing_status}
|
label={f.processing_status}
|
||||||
size="small"
|
size="small"
|
||||||
color={getStatusColor(f.processing_status)}
|
color={getStatusColor(f.processing_status)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -683,7 +683,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
{pagination && pagination.total_pages > 1 && (
|
{pagination && pagination.total_pages > 1 && (
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Stack spacing={1} alignItems="center">
|
<Stack spacing={1} alignItems="center">
|
||||||
<Pagination
|
<Pagination
|
||||||
count={pagination.total_pages}
|
count={pagination.total_pages}
|
||||||
page={pagination.page}
|
page={pagination.page}
|
||||||
onChange={(event, value) => setCurrentPage(value)}
|
onChange={(event, value) => setCurrentPage(value)}
|
||||||
@ -702,24 +702,24 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
{/* Upload Controls */}
|
{/* Upload Controls */}
|
||||||
<Box sx={{ mt: 2, display: 'flex', gap: 1, flexDirection: 'column' }}>
|
<Box sx={{ mt: 2, display: 'flex', gap: 1, flexDirection: 'column' }}>
|
||||||
{/* File Inputs */}
|
{/* File Inputs */}
|
||||||
<input
|
<input
|
||||||
id="cc-file-input"
|
id="cc-file-input"
|
||||||
type="file"
|
type="file"
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleUpload}
|
onChange={handleUpload}
|
||||||
disabled={!selectedCabinet}
|
disabled={!selectedCabinet}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
id="cc-directory-input"
|
id="cc-directory-input"
|
||||||
type="file"
|
type="file"
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
{...({ webkitdirectory: '' } as React.InputHTMLAttributes<HTMLInputElement>)}
|
{...({ webkitdirectory: '' } as React.InputHTMLAttributes<HTMLInputElement>)}
|
||||||
multiple
|
multiple
|
||||||
onChange={handleDirectorySelect}
|
onChange={handleDirectorySelect}
|
||||||
disabled={!selectedCabinet}
|
disabled={!selectedCabinet}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Upload Buttons */}
|
{/* Upload Buttons */}
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
@ -731,7 +731,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Upload File
|
Upload File
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<FolderOpenIcon />}
|
startIcon={<FolderOpenIcon />}
|
||||||
@ -742,13 +742,13 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
Upload Folder
|
Upload Folder
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{!selectedCabinet && (
|
{!selectedCabinet && (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center', mt: 0.5 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center', mt: 0.5 }}>
|
||||||
Select a cabinet first to enable uploads
|
Select a cabinet first to enable uploads
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedCabinet && !isDirectoryPickerSupported() && (
|
{selectedCabinet && !isDirectoryPickerSupported() && (
|
||||||
<Typography variant="caption" color="warning.main" sx={{ textAlign: 'center', mt: 0.5 }}>
|
<Typography variant="caption" color="warning.main" sx={{ textAlign: 'center', mt: 0.5 }}>
|
||||||
⚠️ Folder uploads may have limited support in this browser
|
⚠️ Folder uploads may have limited support in this browser
|
||||||
@ -769,11 +769,11 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
|
|
||||||
{artefacts.length > 0 && (
|
{artefacts.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider/>
|
||||||
<List dense sx={{
|
<List dense sx={{
|
||||||
border: '1px solid var(--color-divider)',
|
border: '1px solid var(--color-divider)',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
maxHeight: 160,
|
maxHeight: 160,
|
||||||
// Hide scrollbar while keeping scroll functionality
|
// Hide scrollbar while keeping scroll functionality
|
||||||
scrollbarWidth: 'none', // Firefox
|
scrollbarWidth: 'none', // Firefox
|
||||||
@ -789,12 +789,12 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Directory Upload Dialog */}
|
{/* Directory Upload Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={showDirectoryDialog}
|
open={showDirectoryDialog}
|
||||||
onClose={() => !isDirectoryUploading && setShowDirectoryDialog(false)}
|
onClose={() => !isDirectoryUploading && setShowDirectoryDialog(false)}
|
||||||
maxWidth="md"
|
maxWidth="md"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@ -804,21 +804,21 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
{isDirectoryUploading && <LinearProgress sx={{ flexGrow: 1, ml: 2 }} />}
|
{isDirectoryUploading && <LinearProgress sx={{ flexGrow: 1, ml: 2 }} />}
|
||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{directoryStats && (
|
{directoryStats && (
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<strong>{directoryStats.fileCount} files</strong> in{' '}
|
<strong>{directoryStats.fileCount} files</strong> in{' '}
|
||||||
<strong>{directoryStats.directoryCount} folders</strong><br />
|
<strong>{directoryStats.directoryCount} folders</strong><br/>
|
||||||
Total size: <strong>{directoryStats.formattedSize}</strong>
|
Total size: <strong>{directoryStats.formattedSize}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Paper variant="outlined" sx={{
|
<Paper variant="outlined" sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
// Hide scrollbar while keeping scroll functionality
|
// Hide scrollbar while keeping scroll functionality
|
||||||
scrollbarWidth: 'none', // Firefox
|
scrollbarWidth: 'none', // Firefox
|
||||||
@ -841,14 +841,14 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</Paper>
|
</Paper>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setShowDirectoryDialog(false)} disabled={isDirectoryUploading}>
|
<Button onClick={() => setShowDirectoryDialog(false)} disabled={isDirectoryUploading}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={startDirectoryUpload}
|
onClick={startDirectoryUpload}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isDirectoryUploading || selectedFiles.length === 0}
|
disabled={isDirectoryUploading || selectedFiles.length === 0}
|
||||||
>
|
>
|
||||||
{isDirectoryUploading ? 'Uploading...' : 'Upload Directory'}
|
{isDirectoryUploading ? 'Uploading...' : 'Upload Directory'}
|
||||||
|
|||||||
48
src/vite-env.d.ts
vendored
48
src/vite-env.d.ts
vendored
@ -1,48 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
|
||||||
// App Information
|
|
||||||
readonly VITE_APP_NAME: string
|
|
||||||
readonly VITE_APP_VERSION: string
|
|
||||||
readonly VITE_APP_DESCRIPTION: string
|
|
||||||
readonly VITE_APP_AUTHOR: string
|
|
||||||
|
|
||||||
// Super Admin Email
|
|
||||||
readonly VITE_SUPER_ADMIN_EMAIL: string
|
|
||||||
|
|
||||||
// Supabase
|
|
||||||
readonly VITE_SUPABASE_PUBLIC_URL: string
|
|
||||||
readonly VITE_SUPABASE_URL: string
|
|
||||||
readonly VITE_SUPABASE_REDIRECT_URL: string
|
|
||||||
readonly VITE_SUPABASE_ANON_KEY: string
|
|
||||||
|
|
||||||
// Site URL
|
|
||||||
readonly VITE_FRONTEND_SITE_URL: string
|
|
||||||
|
|
||||||
// Firebase Settings
|
|
||||||
readonly VITE_REACT_APP_API_KEY: string
|
|
||||||
readonly VITE_REACT_APP_AUTH_DOMAIN: string
|
|
||||||
readonly VITE_REACT_APP_PROJECT_ID: string
|
|
||||||
readonly VITE_REACT_APP_STORAGE_BUCKET: string
|
|
||||||
readonly VITE_REACT_APP_MESSAGING_SENDER_ID: string
|
|
||||||
readonly VITE_REACT_APP_APP_ID: string
|
|
||||||
|
|
||||||
// Supabase Settings
|
|
||||||
|
|
||||||
// Microsoft API Settings
|
|
||||||
readonly VITE_MICROSOFT_CLIENT_ID: string
|
|
||||||
readonly VITE_MICROSOFT_CLIENT_SECRET_DESC: string
|
|
||||||
readonly VITE_MICROSOFT_CLIENT_SECRET_ID: string
|
|
||||||
readonly VITE_MICROSOFT_CLIENT_SECRET: string
|
|
||||||
readonly VITE_MICROSOFT_TENANT_ID: string
|
|
||||||
|
|
||||||
// API Base
|
|
||||||
readonly VITE_API_BASE: string
|
|
||||||
|
|
||||||
// Search URL
|
|
||||||
readonly VITE_SEARCH_URL: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImportMeta {
|
|
||||||
readonly env: ImportMetaEnv
|
|
||||||
}
|
|
||||||
@ -9,10 +9,10 @@ import { VitePWA } from 'vite-plugin-pwa';
|
|||||||
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
|
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
|
||||||
// Load env file based on mode in correct order
|
// Load env file based on mode in correct order
|
||||||
const env = loadEnv(mode, process.cwd(), 'VITE_');
|
const env = loadEnv(mode, process.cwd(), 'VITE_');
|
||||||
|
|
||||||
// Determine base URL from hostname
|
// Determine base URL from hostname
|
||||||
const base = '/'; // Always use root path, let nginx handle the routing
|
const base = '/'; // Always use root path, let nginx handle the routing
|
||||||
|
|
||||||
// Determine if we're in production based on mode and VITE_DEV flag
|
// Determine if we're in production based on mode and VITE_DEV flag
|
||||||
const isProd = mode === 'production' && env.VITE_DEV !== 'true';
|
const isProd = mode === 'production' && env.VITE_DEV !== 'true';
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
|||||||
swDest: 'dist/sw.js',
|
swDest: 'dist/sw.js',
|
||||||
manifestTransforms: [
|
manifestTransforms: [
|
||||||
// Transform manifest entries to ensure proper caching
|
// Transform manifest entries to ensure proper caching
|
||||||
(entries: any[]) => ({
|
(entries) => ({
|
||||||
manifest: entries.map(entry => ({
|
manifest: entries.map(entry => ({
|
||||||
...entry,
|
...entry,
|
||||||
url: entry.url.startsWith(base) ? entry.url : `${base}${entry.url.startsWith('/') ? entry.url.slice(1) : entry.url}`
|
url: entry.url.startsWith(base) ? entry.url : `${base}${entry.url.startsWith('/') ? entry.url.slice(1) : entry.url}`
|
||||||
@ -106,8 +106,6 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
|||||||
'import.meta.env.VITE_SUPABASE_ANON_KEY': JSON.stringify(env.VITE_SUPABASE_ANON_KEY),
|
'import.meta.env.VITE_SUPABASE_ANON_KEY': JSON.stringify(env.VITE_SUPABASE_ANON_KEY),
|
||||||
'import.meta.env.VITE_SUPER_ADMIN_EMAIL': JSON.stringify(env.VITE_SUPER_ADMIN_EMAIL),
|
'import.meta.env.VITE_SUPER_ADMIN_EMAIL': JSON.stringify(env.VITE_SUPER_ADMIN_EMAIL),
|
||||||
'import.meta.env.VITE_DEV': env.VITE_DEV === 'true',
|
'import.meta.env.VITE_DEV': env.VITE_DEV === 'true',
|
||||||
'import.meta.env.VITE_API_BASE': JSON.stringify(env.VITE_API_BASE),
|
|
||||||
'import.meta.env.VITE_SEARCH_URL': JSON.stringify(env.VITE_SEARCH_URL),
|
|
||||||
},
|
},
|
||||||
envPrefix: 'VITE_',
|
envPrefix: 'VITE_',
|
||||||
base,
|
base,
|
||||||
@ -127,13 +125,6 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
|||||||
port: parseInt(env.VITE_PORT_FRONTEND || '5173'),
|
port: parseInt(env.VITE_PORT_FRONTEND || '5173'),
|
||||||
clientPort: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'),
|
clientPort: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'),
|
||||||
overlay: false
|
overlay: false
|
||||||
},
|
|
||||||
proxy: {
|
|
||||||
'/searxng-api': {
|
|
||||||
target: env.VITE_SEARCH_URL,
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path: string) => path.replace(/^\/searxng-api/, '')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
@ -152,6 +143,12 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
|||||||
} : undefined,
|
} : undefined,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
|
||||||
|
'vendor-mui': ['@mui/material', '@mui/icons-material'],
|
||||||
|
'vendor-tldraw': ['@tldraw/tldraw', '@tldraw/store', '@tldraw/tlschema'],
|
||||||
|
'vendor-utils': ['axios', 'zustand', '@supabase/supabase-js']
|
||||||
|
},
|
||||||
// Ensure chunk filenames include content hash
|
// Ensure chunk filenames include content hash
|
||||||
chunkFileNames: isProd ? 'assets/[name].[hash].js' : 'assets/[name].js',
|
chunkFileNames: isProd ? 'assets/[name].[hash].js' : 'assets/[name].js',
|
||||||
assetFileNames: isProd ? 'assets/[name].[hash][extname]' : 'assets/[name][extname]'
|
assetFileNames: isProd ? 'assets/[name].[hash][extname]' : 'assets/[name][extname]'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user