Skip to content

Commit 73bb245

Browse files
Merge pull request Gerome-Elassaad#40 from Gerome-Elassaad/fix/llm-model-selection
enhance
2 parents f0149cc + 112920b commit 73bb245

28 files changed

Lines changed: 1754 additions & 198 deletions

app/api/files/batch/route.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { createServerClient } from '@/lib/supabase-server'
3+
4+
export const dynamic = 'force-dynamic'
5+
export const maxDuration = 300 // 5 minutes for large imports
6+
7+
interface BatchFileInput {
8+
path: string
9+
content: string
10+
isDirectory?: boolean
11+
}
12+
13+
export async function POST(request: NextRequest) {
14+
try {
15+
const body = await request.json()
16+
const { files } = body as { files: BatchFileInput[] }
17+
18+
if (!files || !Array.isArray(files)) {
19+
return NextResponse.json({ error: 'Files array is required' }, { status: 400 })
20+
}
21+
22+
if (files.length === 0) {
23+
return NextResponse.json({ error: 'Files array cannot be empty' }, { status: 400 })
24+
}
25+
26+
// Limit batch size to prevent abuse
27+
if (files.length > 1000) {
28+
return NextResponse.json({ error: 'Maximum 1000 files per batch' }, { status: 400 })
29+
}
30+
31+
const supabase = createServerClient()
32+
33+
// Get authenticated user from session
34+
const { data: { user }, error: authError } = await supabase.auth.getUser()
35+
36+
if (authError || !user) {
37+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
38+
}
39+
40+
// Prepare batch insert data
41+
const insertData = files.map(file => {
42+
const pathParts = file.path.split('/')
43+
const name = pathParts[pathParts.length - 1]
44+
const parentPath = pathParts.length > 1 ? pathParts.slice(0, -1).join('/') : null
45+
const sizeBytes = Buffer.byteLength(file.content || '', 'utf8')
46+
47+
return {
48+
user_id: user.id,
49+
path: file.path,
50+
name,
51+
content: file.content || '',
52+
is_directory: file.isDirectory || false,
53+
parent_path: parentPath,
54+
size_bytes: sizeBytes,
55+
}
56+
})
57+
58+
// Insert all files in a single batch operation
59+
const { data: insertedFiles, error: insertError } = await supabase
60+
.from('workspace_files')
61+
.insert(insertData)
62+
.select()
63+
64+
if (insertError) {
65+
console.error('Error batch inserting files:', insertError)
66+
67+
// Check if it's a duplicate key error
68+
if (insertError.code === '23505') {
69+
return NextResponse.json({
70+
error: 'Some files already exist. Delete them first or use update endpoint.',
71+
details: insertError.message
72+
}, { status: 409 })
73+
}
74+
75+
return NextResponse.json({
76+
error: 'Failed to import files',
77+
details: insertError.message
78+
}, { status: 500 })
79+
}
80+
81+
return NextResponse.json({
82+
success: true,
83+
imported: insertedFiles?.length || 0,
84+
files: insertedFiles
85+
})
86+
} catch (error) {
87+
console.error('Error in POST /api/files/batch:', error)
88+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
89+
}
90+
}

app/api/files/content/route.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { createServerClient } from '@/lib/supabase-server'
3+
4+
export const dynamic = 'force-dynamic'
5+
6+
export async function GET(request: NextRequest) {
7+
try {
8+
const path = request.nextUrl.searchParams.get('path')
9+
10+
if (!path) {
11+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
12+
}
13+
14+
const supabase = createServerClient()
15+
16+
// Get authenticated user from session
17+
const { data: { user }, error: authError } = await supabase.auth.getUser()
18+
19+
if (authError || !user) {
20+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
21+
}
22+
23+
// Fetch the file content
24+
const { data: file, error } = await supabase
25+
.from('workspace_files')
26+
.select('content, path, name, is_directory')
27+
.eq('user_id', user.id)
28+
.eq('path', path)
29+
.single()
30+
31+
if (error) {
32+
console.error('Error fetching file content:', error)
33+
return NextResponse.json({ error: 'File not found' }, { status: 404 })
34+
}
35+
36+
if (file.is_directory) {
37+
return NextResponse.json({ error: 'Cannot read content of a directory' }, { status: 400 })
38+
}
39+
40+
return NextResponse.json({
41+
content: file.content,
42+
path: file.path,
43+
name: file.name
44+
})
45+
} catch (error) {
46+
console.error('Error in GET /api/files/content:', error)
47+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
48+
}
49+
}
50+
51+
export async function POST(request: NextRequest) {
52+
try {
53+
const body = await request.json()
54+
const { path, content } = body
55+
56+
if (!path || content === undefined) {
57+
return NextResponse.json({ error: 'Path and content are required' }, { status: 400 })
58+
}
59+
60+
const supabase = createServerClient()
61+
62+
// Get authenticated user from session
63+
const { data: { user }, error: authError } = await supabase.auth.getUser()
64+
65+
if (authError || !user) {
66+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
67+
}
68+
69+
// Calculate content size
70+
const sizeBytes = Buffer.byteLength(content, 'utf8')
71+
72+
// Update the file content (updated_at handled by database trigger)
73+
const { data: file, error } = await supabase
74+
.from('workspace_files')
75+
.update({
76+
content,
77+
size_bytes: sizeBytes
78+
})
79+
.eq('user_id', user.id)
80+
.eq('path', path)
81+
.select()
82+
.single()
83+
84+
if (error) {
85+
console.error('Error updating file content:', error)
86+
return NextResponse.json({ error: 'Failed to update file' }, { status: 500 })
87+
}
88+
89+
return NextResponse.json({ success: true, file })
90+
} catch (error) {
91+
console.error('Error in POST /api/files/content:', error)
92+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
93+
}
94+
}

app/api/files/route.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { createServerClient } from '@/lib/supabase-server'
3+
4+
export const dynamic = 'force-dynamic'
5+
6+
// Helper function to build file tree from flat list
7+
function buildFileTree(files: any[]): any[] {
8+
const tree: any[] = []
9+
const pathMap = new Map<string, any>()
10+
11+
// Sort files by path to ensure parents are processed before children
12+
const sortedFiles = files.sort((a, b) => a.path.localeCompare(b.path))
13+
14+
for (const file of sortedFiles) {
15+
const node = {
16+
name: file.name,
17+
path: file.path,
18+
isDirectory: file.is_directory,
19+
children: file.is_directory ? [] : undefined,
20+
}
21+
22+
pathMap.set(file.path, node)
23+
24+
if (file.parent_path) {
25+
const parent = pathMap.get(file.parent_path)
26+
if (parent && parent.children) {
27+
parent.children.push(node)
28+
} else {
29+
tree.push(node)
30+
}
31+
} else {
32+
tree.push(node)
33+
}
34+
}
35+
36+
return tree
37+
}
38+
39+
export async function GET(request: NextRequest) {
40+
try {
41+
const supabase = createServerClient()
42+
43+
// Get authenticated user from session
44+
const { data: { user }, error: authError } = await supabase.auth.getUser()
45+
46+
if (authError || !user) {
47+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
48+
}
49+
50+
// Fetch all workspace files for the user
51+
const { data: files, error } = await supabase
52+
.from('workspace_files')
53+
.select('*')
54+
.eq('user_id', user.id)
55+
.order('path', { ascending: true })
56+
57+
if (error) {
58+
console.error('Error fetching workspace files:', error)
59+
return NextResponse.json({ error: 'Failed to fetch files' }, { status: 500 })
60+
}
61+
62+
// Build file tree structure
63+
const fileTree = buildFileTree(files || [])
64+
65+
return NextResponse.json(fileTree)
66+
} catch (error) {
67+
console.error('Error in GET /api/files:', error)
68+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
69+
}
70+
}
71+
72+
export async function POST(request: NextRequest) {
73+
try {
74+
const body = await request.json()
75+
const { path, isDirectory, content = '' } = body
76+
77+
if (!path) {
78+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
79+
}
80+
81+
const supabase = createServerClient()
82+
83+
// Get authenticated user from session
84+
const { data: { user }, error: authError } = await supabase.auth.getUser()
85+
86+
if (authError || !user) {
87+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
88+
}
89+
90+
// Extract file name and parent path
91+
const pathParts = path.split('/')
92+
const name = pathParts[pathParts.length - 1]
93+
const parentPath = pathParts.length > 1 ? pathParts.slice(0, -1).join('/') : null
94+
95+
// Calculate content size
96+
const sizeBytes = Buffer.byteLength(content, 'utf8')
97+
98+
// Insert the new file
99+
const { data: file, error } = await supabase
100+
.from('workspace_files')
101+
.insert({
102+
user_id: user.id,
103+
path,
104+
name,
105+
content,
106+
is_directory: isDirectory,
107+
parent_path: parentPath,
108+
size_bytes: sizeBytes,
109+
})
110+
.select()
111+
.single()
112+
113+
if (error) {
114+
console.error('Error creating workspace file:', error)
115+
return NextResponse.json({ error: 'Failed to create file' }, { status: 500 })
116+
}
117+
118+
return NextResponse.json({ success: true, file })
119+
} catch (error) {
120+
console.error('Error in POST /api/files:', error)
121+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
122+
}
123+
}
124+
125+
export async function DELETE(request: NextRequest) {
126+
try {
127+
const body = await request.json()
128+
const { path } = body
129+
130+
if (!path) {
131+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
132+
}
133+
134+
const supabase = createServerClient()
135+
136+
// Get authenticated user from session
137+
const { data: { user }, error: authError } = await supabase.auth.getUser()
138+
139+
if (authError || !user) {
140+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
141+
}
142+
143+
// Use LIKE to match all nested paths that start with this path
144+
const { error } = await supabase
145+
.from('workspace_files')
146+
.delete()
147+
.eq('user_id', user.id)
148+
.or(`path.eq.${path},path.like.${path}/%`)
149+
150+
if (error) {
151+
console.error('Error deleting workspace file:', error)
152+
return NextResponse.json({ error: 'Failed to delete file' }, { status: 500 })
153+
}
154+
155+
return NextResponse.json({ success: true })
156+
} catch (error) {
157+
console.error('Error in DELETE /api/files:', error)
158+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
159+
}
160+
}

0 commit comments

Comments
 (0)