Compare commits
2 Commits
3472f203b9
...
5c100a015d
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c100a015d | |||
| 5e5aad54cb |
17
.env
17
.env
@ -1,17 +0,0 @@
|
|||||||
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
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
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,19 +20,24 @@ 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
59
dist/.vite/manifest.json
vendored
@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
"_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
68827
dist/assets/index-CmYeIoD0.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-CmYeIoD0.js.map
vendored
1
dist/assets/index-CmYeIoD0.js.map
vendored
File diff suppressed because one or more lines are too long
11178
dist/assets/index.css
vendored
11178
dist/assets/index.css
vendored
File diff suppressed because it is too large
Load Diff
21232
dist/assets/pdf.js
vendored
21232
dist/assets/pdf.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/assets/pdf.js.map
vendored
1
dist/assets/pdf.js.map
vendored
File diff suppressed because one or more lines are too long
21
dist/assets/pdf.worker.min.mjs
vendored
21
dist/assets/pdf.worker.min.mjs
vendored
File diff suppressed because one or more lines are too long
19850
dist/assets/vendor-mui.js
vendored
19850
dist/assets/vendor-mui.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-mui.js.map
vendored
1
dist/assets/vendor-mui.js.map
vendored
File diff suppressed because one or more lines are too long
1901
dist/assets/vendor-react.js
vendored
1901
dist/assets/vendor-react.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-react.js.map
vendored
1
dist/assets/vendor-react.js.map
vendored
File diff suppressed because one or more lines are too long
68796
dist/assets/vendor-tldraw.js
vendored
68796
dist/assets/vendor-tldraw.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/vendor-tldraw.js.map
vendored
1
dist/assets/vendor-tldraw.js.map
vendored
File diff suppressed because one or more lines are too long
12264
dist/assets/vendor-utils.js
vendored
12264
dist/assets/vendor-utils.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/assets/vendor-utils.js.map
vendored
1
dist/assets/vendor-utils.js.map
vendored
File diff suppressed because one or more lines are too long
12
dist/audioWorklet.js
vendored
12
dist/audioWorklet.js
vendored
@ -1,12 +0,0 @@
|
|||||||
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
BIN
dist/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
BIN
dist/icons/icon-192x192-maskable.png
vendored
BIN
dist/icons/icon-192x192-maskable.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
BIN
dist/icons/icon-192x192.png
vendored
BIN
dist/icons/icon-192x192.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
BIN
dist/icons/icon-512x512-maskable.png
vendored
BIN
dist/icons/icon-512x512-maskable.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 259 KiB |
BIN
dist/icons/icon-512x512.png
vendored
BIN
dist/icons/icon-512x512.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 259 KiB |
21
dist/icons/sticker-tool.svg
vendored
21
dist/icons/sticker-tool.svg
vendored
@ -1,21 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 689 B |
18
dist/index.html
vendored
18
dist/index.html
vendored
@ -1,18 +0,0 @@
|
|||||||
<!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
1
dist/manifest.webmanifest
vendored
@ -1 +0,0 @@
|
|||||||
{"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
70
dist/offline.html
vendored
@ -1,70 +0,0 @@
|
|||||||
<!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
1
dist/registerSW.js
vendored
@ -1 +0,0 @@
|
|||||||
if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js', { scope: '/' })})}
|
|
||||||
3889
dist/sw.js
vendored
3889
dist/sw.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/sw.js.map
vendored
1
dist/sw.js.map
vendored
File diff suppressed because one or more lines are too long
16525
package-lock.json
generated
16525
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -57,14 +57,6 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
@ -90,7 +82,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@ -8,6 +8,9 @@ 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;
|
||||||
@ -29,9 +32,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 }) => {
|
||||||
@ -110,7 +113,7 @@ 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
|
||||||
@ -131,9 +134,9 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
queryStarted: true
|
queryStarted: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error } = await fetch(`http://localhost:8000/rest/v1/profiles?select=*&id=eq.${userInfo.id}`, {
|
const { data, error } = await fetch(`${supabaseUrl}/rest/v1/profiles?select=*&id=eq.${userInfo.id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0`,
|
'Authorization': `Bearer ${supabaseAnonKey}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -110,7 +110,8 @@ 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
|
||||||
@ -338,6 +339,7 @@ logger.setConfig({
|
|||||||
'user-context',
|
'user-context',
|
||||||
'neo-user-context',
|
'neo-user-context',
|
||||||
'neo-institute-context',
|
'neo-institute-context',
|
||||||
|
'search-service'
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -823,7 +823,7 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
<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>
|
||||||
@ -843,12 +843,12 @@ const SimpleUploadTest: React.FC = () => {
|
|||||||
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>
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import logger from "../../debugConfig"
|
||||||
|
|
||||||
interface SearXNGResult {
|
interface SearXNGResult {
|
||||||
title?: string
|
title?: string
|
||||||
url?: string
|
url?: string
|
||||||
@ -26,19 +28,25 @@ export class SearchService {
|
|||||||
engines: 'google,bing,duckduckgo',
|
engines: 'google,bing,duckduckgo',
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `${import.meta.env.VITE_FRONTEND_SITE_URL}/searxng-api/search?${searchParams.toString()}`
|
const 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 || '',
|
||||||
|
|||||||
14
src/sw.ts
14
src/sw.ts
@ -95,8 +95,8 @@ const navigationHandler = async ({ request }: { request: Request }): Promise<Res
|
|||||||
|
|
||||||
// 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;
|
||||||
@ -169,9 +169,9 @@ 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 as unknown as { env?: { VITE_API_BASE?: string } })?.env?.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
const API_BASE: string = import.meta.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 => (
|
||||||
|
|||||||
@ -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 as unknown as { env?: { VITE_API_BASE?: string } })?.env?.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api');
|
const API_BASE: string = import.meta.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 = {
|
||||||
@ -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 => {
|
||||||
@ -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,10 +593,10 @@ 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>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -632,10 +632,10 @@ 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>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -769,7 +769,7 @@ 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',
|
||||||
@ -810,7 +810,7 @@ export const CCFilesPanel: React.FC = () => {
|
|||||||
<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>
|
||||||
|
|||||||
48
src/vite-env.d.ts
vendored
Normal file
48
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/// <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
|
||||||
|
}
|
||||||
@ -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) => ({
|
(entries: any[]) => ({
|
||||||
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,6 +106,8 @@ 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,
|
||||||
@ -125,6 +127,13 @@ 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,
|
||||||
@ -143,12 +152,6 @@ 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