launchbase 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/launchbase.js +1 -1
- package/package.json +1 -1
- package/template/frontend/src/lib/api.ts +93 -0
- package/template/frontend/src/pages/EdgeFunctions.tsx +16 -12
- package/template/src/modules/app/app.module.ts +7 -1
- package/template/src/modules/deployments/deployments.controller.ts +47 -0
- package/template/src/modules/deployments/deployments.module.ts +12 -0
- package/template/src/modules/deployments/deployments.service.ts +88 -0
- package/template/src/modules/deployments/dto/create-deployment.dto.ts +12 -0
- package/template/src/modules/edge-functions/edge-functions.controller.ts +17 -17
- package/template/src/modules/edge-functions/edge-functions.service.ts +17 -17
- package/template/src/modules/projects/projects.service.ts +11 -8
- package/template/src/modules/vector/vector.controller.ts +19 -19
- package/template/src/modules/vector/vector.service.ts +27 -27
package/bin/launchbase.js
CHANGED
|
@@ -7,7 +7,7 @@ const crypto = require('crypto');
|
|
|
7
7
|
const fs = require('fs-extra');
|
|
8
8
|
const { execSync, spawn } = require('child_process');
|
|
9
9
|
|
|
10
|
-
const VERSION = '1.1.
|
|
10
|
+
const VERSION = '1.1.8';
|
|
11
11
|
const program = new Command();
|
|
12
12
|
|
|
13
13
|
function findAvailablePort(startPort = 5432, maxAttempts = 100) {
|
package/package.json
CHANGED
|
@@ -166,3 +166,96 @@ export const filesApi = {
|
|
|
166
166
|
await api.delete(`/api/files/${id}`)
|
|
167
167
|
},
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
// Edge Functions API
|
|
171
|
+
export const edgeFunctionsApi = {
|
|
172
|
+
list: async (projectId: string) => {
|
|
173
|
+
const res = await api.get(`/api/projects/${projectId}/functions`)
|
|
174
|
+
return res.data
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
create: async (projectId: string, data: { name: string; code: string; description?: string }) => {
|
|
178
|
+
const res = await api.post(`/api/projects/${projectId}/functions`, data)
|
|
179
|
+
return res.data
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
get: async (projectId: string, functionId: string) => {
|
|
183
|
+
const res = await api.get(`/api/projects/${projectId}/functions/${functionId}`)
|
|
184
|
+
return res.data
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
update: async (projectId: string, functionId: string, data: Partial<{ name: string; code: string; description: string }>) => {
|
|
188
|
+
const res = await api.put(`/api/projects/${projectId}/functions/${functionId}`, data)
|
|
189
|
+
return res.data
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
delete: async (projectId: string, functionId: string) => {
|
|
193
|
+
await api.delete(`/api/projects/${projectId}/functions/${functionId}`)
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
deploy: async (projectId: string, functionId: string) => {
|
|
197
|
+
const res = await api.post(`/api/projects/${projectId}/functions/${functionId}/deploy`)
|
|
198
|
+
return res.data
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
execute: async (projectId: string, functionId: string, payload: any) => {
|
|
202
|
+
const res = await api.post(`/api/projects/${projectId}/functions/${functionId}/execute`, { payload })
|
|
203
|
+
return res.data
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
getLogs: async (projectId: string, functionId: string) => {
|
|
207
|
+
const res = await api.get(`/api/projects/${projectId}/functions/${functionId}/logs`)
|
|
208
|
+
return res.data
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Vector Collections API
|
|
213
|
+
export const vectorApi = {
|
|
214
|
+
listCollections: async (projectId: string) => {
|
|
215
|
+
const res = await api.get(`/api/projects/${projectId}/vector/collections`)
|
|
216
|
+
return res.data
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
createCollection: async (projectId: string, data: { name: string; description?: string }) => {
|
|
220
|
+
const res = await api.post(`/api/projects/${projectId}/vector/collections`, data)
|
|
221
|
+
return res.data
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
createEmbedding: async (projectId: string, data: { content: string; collectionId: string; metadata?: any }) => {
|
|
225
|
+
const res = await api.post(`/api/projects/${projectId}/vector/embeddings`, data)
|
|
226
|
+
return res.data
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
search: async (projectId: string, data: { query: string; collectionId: string; limit?: number }) => {
|
|
230
|
+
const res = await api.post(`/api/projects/${projectId}/vector/search`, data)
|
|
231
|
+
return res.data
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
query: async (projectId: string, query: string, collection?: string, limit?: number) => {
|
|
235
|
+
const res = await api.post(`/api/projects/${projectId}/vector/query`, { query, collection, limit })
|
|
236
|
+
return res.data
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Deployments API
|
|
241
|
+
export const deploymentsApi = {
|
|
242
|
+
list: async (projectId: string) => {
|
|
243
|
+
const res = await api.get(`/api/projects/${projectId}/deployments`)
|
|
244
|
+
return res.data
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
create: async (projectId: string, data: { version: string; description?: string }) => {
|
|
248
|
+
const res = await api.post(`/api/projects/${projectId}/deployments`, data)
|
|
249
|
+
return res.data
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
get: async (projectId: string, deploymentId: string) => {
|
|
253
|
+
const res = await api.get(`/api/projects/${projectId}/deployments/${deploymentId}`)
|
|
254
|
+
return res.data
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
retry: async (projectId: string, deploymentId: string) => {
|
|
258
|
+
const res = await api.post(`/api/projects/${projectId}/deployments/${deploymentId}/retry`)
|
|
259
|
+
return res.data
|
|
260
|
+
},
|
|
261
|
+
}
|
|
@@ -37,7 +37,11 @@ const RUNTIMES = [
|
|
|
37
37
|
{ value: 'go121', label: 'Go 1.21' },
|
|
38
38
|
]
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
interface EdgeFunctionsProps {
|
|
41
|
+
projectId: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function EdgeFunctions({ projectId }: EdgeFunctionsProps) {
|
|
41
45
|
const queryClient = useQueryClient()
|
|
42
46
|
const toast = useToast()
|
|
43
47
|
|
|
@@ -60,21 +64,21 @@ export function EdgeFunctions() {
|
|
|
60
64
|
|
|
61
65
|
// Fetch functions
|
|
62
66
|
const { data: functionsData, isLoading } = useQuery({
|
|
63
|
-
queryKey: ['edge-functions'],
|
|
67
|
+
queryKey: ['edge-functions', projectId],
|
|
64
68
|
queryFn: async () => {
|
|
65
|
-
const res = await fetch(
|
|
69
|
+
const res = await fetch(`/api/projects/${projectId}/functions`)
|
|
66
70
|
if (!res.ok) throw new Error('Failed to fetch functions')
|
|
67
71
|
return res.json()
|
|
68
72
|
}
|
|
69
73
|
})
|
|
70
74
|
|
|
71
|
-
const functions = functionsData?.functions || []
|
|
75
|
+
const functions = functionsData?.functions || functionsData || []
|
|
72
76
|
const stats = functionsData?.stats || { total: 0, deployed: 0, draft: 0, invocations: 0 }
|
|
73
77
|
|
|
74
78
|
// Create function mutation
|
|
75
79
|
const createMutation = useMutation({
|
|
76
80
|
mutationFn: async () => {
|
|
77
|
-
const res = await fetch(
|
|
81
|
+
const res = await fetch(`/api/projects/${projectId}/functions`, {
|
|
78
82
|
method: 'POST',
|
|
79
83
|
headers: { 'Content-Type': 'application/json' },
|
|
80
84
|
body: JSON.stringify(newFunction)
|
|
@@ -86,7 +90,7 @@ export function EdgeFunctions() {
|
|
|
86
90
|
return res.json()
|
|
87
91
|
},
|
|
88
92
|
onSuccess: () => {
|
|
89
|
-
queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
|
|
93
|
+
queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
|
|
90
94
|
setShowCreateModal(false)
|
|
91
95
|
setNewFunction({
|
|
92
96
|
name: '',
|
|
@@ -107,7 +111,7 @@ export function EdgeFunctions() {
|
|
|
107
111
|
// Deploy function mutation
|
|
108
112
|
const deployMutation = useMutation({
|
|
109
113
|
mutationFn: async (functionId: string) => {
|
|
110
|
-
const res = await fetch(`/api/functions/${functionId}/deploy`, {
|
|
114
|
+
const res = await fetch(`/api/projects/${projectId}/functions/${functionId}/deploy`, {
|
|
111
115
|
method: 'POST'
|
|
112
116
|
})
|
|
113
117
|
if (!res.ok) {
|
|
@@ -117,7 +121,7 @@ export function EdgeFunctions() {
|
|
|
117
121
|
return res.json()
|
|
118
122
|
},
|
|
119
123
|
onSuccess: () => {
|
|
120
|
-
queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
|
|
124
|
+
queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
|
|
121
125
|
toast.success('Function deployed successfully')
|
|
122
126
|
},
|
|
123
127
|
onError: (error: Error) => {
|
|
@@ -128,7 +132,7 @@ export function EdgeFunctions() {
|
|
|
128
132
|
// Delete function mutation
|
|
129
133
|
const deleteMutation = useMutation({
|
|
130
134
|
mutationFn: async (functionId: string) => {
|
|
131
|
-
const res = await fetch(`/api/functions/${functionId}`, {
|
|
135
|
+
const res = await fetch(`/api/projects/${projectId}/functions/${functionId}`, {
|
|
132
136
|
method: 'DELETE'
|
|
133
137
|
})
|
|
134
138
|
if (!res.ok) {
|
|
@@ -138,7 +142,7 @@ export function EdgeFunctions() {
|
|
|
138
142
|
return res.json()
|
|
139
143
|
},
|
|
140
144
|
onSuccess: () => {
|
|
141
|
-
queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
|
|
145
|
+
queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
|
|
142
146
|
toast.success('Function deleted successfully')
|
|
143
147
|
},
|
|
144
148
|
onError: (error: Error) => {
|
|
@@ -149,10 +153,10 @@ export function EdgeFunctions() {
|
|
|
149
153
|
// Fetch logs
|
|
150
154
|
const fetchLogs = async (functionId: string) => {
|
|
151
155
|
try {
|
|
152
|
-
const res = await fetch(`/api/functions/${functionId}/logs`)
|
|
156
|
+
const res = await fetch(`/api/projects/${projectId}/functions/${functionId}/logs`)
|
|
153
157
|
if (res.ok) {
|
|
154
158
|
const data = await res.json()
|
|
155
|
-
setFunctionLogs(data.logs || [])
|
|
159
|
+
setFunctionLogs(data.logs || data || [])
|
|
156
160
|
}
|
|
157
161
|
} catch (error) {
|
|
158
162
|
console.error('Failed to fetch logs:', error)
|
|
@@ -15,6 +15,9 @@ import { WebsocketModule } from '../websocket/websocket.module';
|
|
|
15
15
|
import { HealthModule } from '../health/health.module';
|
|
16
16
|
import { DatabaseModule } from '../database/database.module';
|
|
17
17
|
import { AiModule } from '../ai/ai.module';
|
|
18
|
+
import { EdgeFunctionsModule } from '../edge-functions/edge-functions.module';
|
|
19
|
+
import { VectorModule } from '../vector/vector.module';
|
|
20
|
+
import { DeploymentsModule } from '../deployments/deployments.module';
|
|
18
21
|
|
|
19
22
|
// QueueModule is optional - only loaded when Redis is configured
|
|
20
23
|
let QueueModule: any = null;
|
|
@@ -47,7 +50,10 @@ try {
|
|
|
47
50
|
DatabaseModule,
|
|
48
51
|
// Only include QueueModule if Redis is configured
|
|
49
52
|
...(process.env.REDIS_HOST && QueueModule ? [QueueModule] : []),
|
|
50
|
-
AiModule
|
|
53
|
+
AiModule,
|
|
54
|
+
EdgeFunctionsModule,
|
|
55
|
+
VectorModule,
|
|
56
|
+
DeploymentsModule,
|
|
51
57
|
],
|
|
52
58
|
providers: [
|
|
53
59
|
{
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Controller, Post, Get, Body, Param, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
3
|
+
import { JwtAuthGuard } from '../common/jwt-auth.guard';
|
|
4
|
+
import { TenantGuard } from '../common/tenant.guard';
|
|
5
|
+
import { DeploymentsService } from './deployments.service';
|
|
6
|
+
import { CreateDeploymentDto } from './dto/create-deployment.dto';
|
|
7
|
+
|
|
8
|
+
@ApiTags('deployments')
|
|
9
|
+
@ApiBearerAuth()
|
|
10
|
+
@UseGuards(JwtAuthGuard, TenantGuard)
|
|
11
|
+
@Controller('projects/:projectId/deployments')
|
|
12
|
+
export class DeploymentsController {
|
|
13
|
+
constructor(private readonly deploymentsService: DeploymentsService) {}
|
|
14
|
+
|
|
15
|
+
@Post()
|
|
16
|
+
@ApiOperation({ summary: 'Create a new deployment' })
|
|
17
|
+
async create(
|
|
18
|
+
@Param('projectId') projectId: string,
|
|
19
|
+
@Body() dto: CreateDeploymentDto,
|
|
20
|
+
) {
|
|
21
|
+
return this.deploymentsService.create(projectId, dto);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Get()
|
|
25
|
+
@ApiOperation({ summary: 'List all deployments' })
|
|
26
|
+
async findAll(@Param('projectId') projectId: string) {
|
|
27
|
+
return this.deploymentsService.findAll(projectId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Get(':deploymentId')
|
|
31
|
+
@ApiOperation({ summary: 'Get deployment details' })
|
|
32
|
+
async findOne(
|
|
33
|
+
@Param('projectId') projectId: string,
|
|
34
|
+
@Param('deploymentId') deploymentId: string,
|
|
35
|
+
) {
|
|
36
|
+
return this.deploymentsService.findOne(projectId, deploymentId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Post(':deploymentId/retry')
|
|
40
|
+
@ApiOperation({ summary: 'Retry a failed deployment' })
|
|
41
|
+
async retry(
|
|
42
|
+
@Param('projectId') projectId: string,
|
|
43
|
+
@Param('deploymentId') deploymentId: string,
|
|
44
|
+
) {
|
|
45
|
+
return this.deploymentsService.retry(projectId, deploymentId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { DeploymentsController } from './deployments.controller';
|
|
3
|
+
import { DeploymentsService } from './deployments.service';
|
|
4
|
+
import { PrismaModule } from '../prisma/prisma.module';
|
|
5
|
+
|
|
6
|
+
@Module({
|
|
7
|
+
imports: [PrismaModule],
|
|
8
|
+
controllers: [DeploymentsController],
|
|
9
|
+
providers: [DeploymentsService],
|
|
10
|
+
exports: [DeploymentsService],
|
|
11
|
+
})
|
|
12
|
+
export class DeploymentsModule {}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
|
2
|
+
import { PrismaService } from '../prisma/prisma.service';
|
|
3
|
+
import { CreateDeploymentDto } from './dto/create-deployment.dto';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class DeploymentsService {
|
|
7
|
+
constructor(private prisma: PrismaService) {}
|
|
8
|
+
|
|
9
|
+
async create(projectId: string, dto: CreateDeploymentDto) {
|
|
10
|
+
// Get project to verify it exists
|
|
11
|
+
const project = await this.prisma.project.findUnique({
|
|
12
|
+
where: { id: projectId },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!project) {
|
|
16
|
+
throw new NotFoundException('Project not found');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const deployment = await this.prisma.deployment.create({
|
|
20
|
+
data: {
|
|
21
|
+
version: dto.version,
|
|
22
|
+
description: dto.description,
|
|
23
|
+
projectId,
|
|
24
|
+
status: 'pending',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Simulate deployment process (in real app, this would trigger CI/CD)
|
|
29
|
+
this.simulateDeployment(deployment.id);
|
|
30
|
+
|
|
31
|
+
return deployment;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async findAll(projectId: string) {
|
|
35
|
+
return this.prisma.deployment.findMany({
|
|
36
|
+
where: { projectId },
|
|
37
|
+
orderBy: { createdAt: 'desc' },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async findOne(projectId: string, deploymentId: string) {
|
|
42
|
+
const deployment = await this.prisma.deployment.findFirst({
|
|
43
|
+
where: { id: deploymentId, projectId },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!deployment) {
|
|
47
|
+
throw new NotFoundException('Deployment not found');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return deployment;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async retry(projectId: string, deploymentId: string) {
|
|
54
|
+
const deployment = await this.findOne(projectId, deploymentId);
|
|
55
|
+
|
|
56
|
+
if (deployment.status !== 'failed') {
|
|
57
|
+
throw new BadRequestException('Only failed deployments can be retried');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const updated = await this.prisma.deployment.update({
|
|
61
|
+
where: { id: deploymentId },
|
|
62
|
+
data: { status: 'pending', error: null },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Simulate deployment process
|
|
66
|
+
this.simulateDeployment(deploymentId);
|
|
67
|
+
|
|
68
|
+
return updated;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private simulateDeployment(deploymentId: string) {
|
|
72
|
+
// In a real implementation, this would trigger actual deployment
|
|
73
|
+
// For now, we just update the status after a delay
|
|
74
|
+
setTimeout(async () => {
|
|
75
|
+
try {
|
|
76
|
+
await this.prisma.deployment.update({
|
|
77
|
+
where: { id: deploymentId },
|
|
78
|
+
data: {
|
|
79
|
+
status: 'success',
|
|
80
|
+
deployedAt: new Date(),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
} catch {
|
|
84
|
+
// Deployment might have been deleted
|
|
85
|
+
}
|
|
86
|
+
}, 5000);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -8,80 +8,80 @@ import { CreateEdgeFunctionDto, UpdateEdgeFunctionDto, ExecuteEdgeFunctionDto }
|
|
|
8
8
|
@ApiTags('edge-functions')
|
|
9
9
|
@ApiBearerAuth()
|
|
10
10
|
@UseGuards(JwtAuthGuard, TenantGuard)
|
|
11
|
-
@Controller('
|
|
11
|
+
@Controller('projects/:projectId/functions')
|
|
12
12
|
export class EdgeFunctionsController {
|
|
13
13
|
constructor(private readonly edgeFunctionsService: EdgeFunctionsService) {}
|
|
14
14
|
|
|
15
15
|
@Post()
|
|
16
16
|
@ApiOperation({ summary: 'Create a new edge function' })
|
|
17
17
|
async create(
|
|
18
|
-
@Param('
|
|
18
|
+
@Param('projectId') projectId: string,
|
|
19
19
|
@Body() dto: CreateEdgeFunctionDto,
|
|
20
20
|
@Request() req: any,
|
|
21
21
|
) {
|
|
22
|
-
return this.edgeFunctionsService.create(
|
|
22
|
+
return this.edgeFunctionsService.create(projectId, req.user.id, dto);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
@Get()
|
|
26
26
|
@ApiOperation({ summary: 'List all edge functions' })
|
|
27
|
-
async findAll(@Param('
|
|
28
|
-
return this.edgeFunctionsService.findAll(
|
|
27
|
+
async findAll(@Param('projectId') projectId: string) {
|
|
28
|
+
return this.edgeFunctionsService.findAll(projectId);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
@Get(':functionId')
|
|
32
32
|
@ApiOperation({ summary: 'Get edge function details' })
|
|
33
33
|
async findOne(
|
|
34
|
-
@Param('
|
|
34
|
+
@Param('projectId') projectId: string,
|
|
35
35
|
@Param('functionId') functionId: string,
|
|
36
36
|
) {
|
|
37
|
-
return this.edgeFunctionsService.findOne(
|
|
37
|
+
return this.edgeFunctionsService.findOne(projectId, functionId);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@Put(':functionId')
|
|
41
41
|
@ApiOperation({ summary: 'Update edge function' })
|
|
42
42
|
async update(
|
|
43
|
-
@Param('
|
|
43
|
+
@Param('projectId') projectId: string,
|
|
44
44
|
@Param('functionId') functionId: string,
|
|
45
45
|
@Body() dto: UpdateEdgeFunctionDto,
|
|
46
46
|
) {
|
|
47
|
-
return this.edgeFunctionsService.update(
|
|
47
|
+
return this.edgeFunctionsService.update(projectId, functionId, dto);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
@Delete(':functionId')
|
|
51
51
|
@ApiOperation({ summary: 'Delete edge function' })
|
|
52
52
|
async remove(
|
|
53
|
-
@Param('
|
|
53
|
+
@Param('projectId') projectId: string,
|
|
54
54
|
@Param('functionId') functionId: string,
|
|
55
55
|
) {
|
|
56
|
-
return this.edgeFunctionsService.remove(
|
|
56
|
+
return this.edgeFunctionsService.remove(projectId, functionId);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
@Post(':functionId/execute')
|
|
60
60
|
@ApiOperation({ summary: 'Execute edge function' })
|
|
61
61
|
async execute(
|
|
62
|
-
@Param('
|
|
62
|
+
@Param('projectId') projectId: string,
|
|
63
63
|
@Param('functionId') functionId: string,
|
|
64
64
|
@Body() dto: ExecuteEdgeFunctionDto,
|
|
65
65
|
@Request() req: any,
|
|
66
66
|
) {
|
|
67
|
-
return this.edgeFunctionsService.execute(
|
|
67
|
+
return this.edgeFunctionsService.execute(projectId, functionId, dto, req.user);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
@Post(':functionId/deploy')
|
|
71
71
|
@ApiOperation({ summary: 'Deploy edge function to edge network' })
|
|
72
72
|
async deploy(
|
|
73
|
-
@Param('
|
|
73
|
+
@Param('projectId') projectId: string,
|
|
74
74
|
@Param('functionId') functionId: string,
|
|
75
75
|
) {
|
|
76
|
-
return this.edgeFunctionsService.deploy(
|
|
76
|
+
return this.edgeFunctionsService.deploy(projectId, functionId);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
@Get(':functionId/logs')
|
|
80
80
|
@ApiOperation({ summary: 'Get edge function execution logs' })
|
|
81
81
|
async getLogs(
|
|
82
|
-
@Param('
|
|
82
|
+
@Param('projectId') projectId: string,
|
|
83
83
|
@Param('functionId') functionId: string,
|
|
84
84
|
) {
|
|
85
|
-
return this.edgeFunctionsService.getLogs(
|
|
85
|
+
return this.edgeFunctionsService.getLogs(projectId, functionId);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -8,7 +8,7 @@ import * as crypto from 'crypto';
|
|
|
8
8
|
export class EdgeFunctionsService {
|
|
9
9
|
constructor(private prisma: PrismaService) {}
|
|
10
10
|
|
|
11
|
-
async create(
|
|
11
|
+
async create(projectId: string, userId: string, dto: CreateEdgeFunctionDto) {
|
|
12
12
|
// Validate the function code
|
|
13
13
|
this.validateCode(dto.code);
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ export class EdgeFunctionsService {
|
|
|
22
22
|
memory: dto.memory || 128,
|
|
23
23
|
environment: dto.environment || {},
|
|
24
24
|
triggers: dto.triggers || [],
|
|
25
|
-
|
|
25
|
+
projectId,
|
|
26
26
|
createdBy: userId,
|
|
27
27
|
},
|
|
28
28
|
});
|
|
@@ -30,16 +30,16 @@ export class EdgeFunctionsService {
|
|
|
30
30
|
return func;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
async findAll(
|
|
33
|
+
async findAll(projectId: string) {
|
|
34
34
|
return this.prisma.edgeFunction.findMany({
|
|
35
|
-
where: {
|
|
35
|
+
where: { projectId },
|
|
36
36
|
orderBy: { createdAt: 'desc' },
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async findOne(
|
|
40
|
+
async findOne(projectId: string, functionId: string) {
|
|
41
41
|
const func = await this.prisma.edgeFunction.findFirst({
|
|
42
|
-
where: { id: functionId,
|
|
42
|
+
where: { id: functionId, projectId },
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
if (!func) {
|
|
@@ -49,8 +49,8 @@ export class EdgeFunctionsService {
|
|
|
49
49
|
return func;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async update(
|
|
53
|
-
await this.findOne(
|
|
52
|
+
async update(projectId: string, functionId: string, dto: UpdateEdgeFunctionDto) {
|
|
53
|
+
await this.findOne(projectId, functionId);
|
|
54
54
|
|
|
55
55
|
if (dto.code) {
|
|
56
56
|
this.validateCode(dto.code);
|
|
@@ -66,8 +66,8 @@ export class EdgeFunctionsService {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
async remove(
|
|
70
|
-
await this.findOne(
|
|
69
|
+
async remove(projectId: string, functionId: string) {
|
|
70
|
+
await this.findOne(projectId, functionId);
|
|
71
71
|
|
|
72
72
|
await this.prisma.edgeFunction.delete({
|
|
73
73
|
where: { id: functionId },
|
|
@@ -76,8 +76,8 @@ export class EdgeFunctionsService {
|
|
|
76
76
|
return { success: true };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
async execute(
|
|
80
|
-
const func = await this.findOne(
|
|
79
|
+
async execute(projectId: string, functionId: string, dto: ExecuteEdgeFunctionDto, user: any) {
|
|
80
|
+
const func = await this.findOne(projectId, functionId);
|
|
81
81
|
|
|
82
82
|
if (func.status !== 'deployed') {
|
|
83
83
|
throw new BadRequestException('Function must be deployed before execution');
|
|
@@ -93,7 +93,7 @@ export class EdgeFunctionsService {
|
|
|
93
93
|
result = await this.runInSandbox(func.code, {
|
|
94
94
|
...dto.payload,
|
|
95
95
|
user: { id: user.id, email: user.email },
|
|
96
|
-
|
|
96
|
+
projectId,
|
|
97
97
|
env: func.environment as Record<string, string>,
|
|
98
98
|
});
|
|
99
99
|
} catch (err: any) {
|
|
@@ -123,8 +123,8 @@ export class EdgeFunctionsService {
|
|
|
123
123
|
return result;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async deploy(
|
|
127
|
-
const func = await this.findOne(
|
|
126
|
+
async deploy(projectId: string, functionId: string) {
|
|
127
|
+
const func = await this.findOne(projectId, functionId);
|
|
128
128
|
|
|
129
129
|
// Generate deployment info
|
|
130
130
|
const deploymentId = crypto.randomUUID();
|
|
@@ -147,8 +147,8 @@ export class EdgeFunctionsService {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
async getLogs(
|
|
151
|
-
await this.findOne(
|
|
150
|
+
async getLogs(projectId: string, functionId: string) {
|
|
151
|
+
await this.findOne(projectId, functionId);
|
|
152
152
|
|
|
153
153
|
return this.prisma.edgeFunctionLog.findMany({
|
|
154
154
|
where: { functionId },
|
|
@@ -7,27 +7,30 @@ import { UpdateProjectDto } from './dto/update-project.dto';
|
|
|
7
7
|
export class ProjectsService {
|
|
8
8
|
constructor(private readonly prisma: PrismaService) {}
|
|
9
9
|
|
|
10
|
-
async list(
|
|
10
|
+
async list(organizationId: string) {
|
|
11
11
|
const projects = await this.prisma.project.findMany({
|
|
12
|
-
where: {
|
|
12
|
+
where: { organizationId },
|
|
13
13
|
orderBy: { createdAt: 'desc' }
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
return { projects };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async create(
|
|
19
|
+
async create(organizationId: string, dto: CreateProjectDto) {
|
|
20
|
+
// Generate slug from project name
|
|
21
|
+
const slug = dto.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
22
|
+
|
|
20
23
|
const project = await this.prisma.project.create({
|
|
21
|
-
data: {
|
|
24
|
+
data: { organizationId, name: dto.name, slug }
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
return { project };
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
async update(
|
|
30
|
+
async update(organizationId: string, id: string, dto: UpdateProjectDto) {
|
|
28
31
|
const project = await this.prisma.project.findUnique({ where: { id } });
|
|
29
32
|
if (!project) throw new NotFoundException('Project not found');
|
|
30
|
-
if (project.
|
|
33
|
+
if (project.organizationId !== organizationId) throw new ForbiddenException('Forbidden');
|
|
31
34
|
|
|
32
35
|
const updated = await this.prisma.project.update({
|
|
33
36
|
where: { id },
|
|
@@ -37,10 +40,10 @@ export class ProjectsService {
|
|
|
37
40
|
return { project: updated };
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
async remove(
|
|
43
|
+
async remove(organizationId: string, id: string) {
|
|
41
44
|
const project = await this.prisma.project.findUnique({ where: { id } });
|
|
42
45
|
if (!project) throw new NotFoundException('Project not found');
|
|
43
|
-
if (project.
|
|
46
|
+
if (project.organizationId !== organizationId) throw new ForbiddenException('Forbidden');
|
|
44
47
|
|
|
45
48
|
await this.prisma.project.delete({ where: { id } });
|
|
46
49
|
return { deleted: true };
|
|
@@ -8,88 +8,88 @@ import { CreateEmbeddingDto, SearchSimilarDto, CreateCollectionDto } from './dto
|
|
|
8
8
|
@ApiTags('vector')
|
|
9
9
|
@ApiBearerAuth()
|
|
10
10
|
@UseGuards(JwtAuthGuard, TenantGuard)
|
|
11
|
-
@Controller('
|
|
11
|
+
@Controller('projects/:projectId/vector')
|
|
12
12
|
export class VectorController {
|
|
13
13
|
constructor(private readonly vectorService: VectorService) {}
|
|
14
14
|
|
|
15
15
|
@Post('collections')
|
|
16
16
|
@ApiOperation({ summary: 'Create a vector collection' })
|
|
17
17
|
async createCollection(
|
|
18
|
-
@Param('
|
|
18
|
+
@Param('projectId') projectId: string,
|
|
19
19
|
@Body() dto: CreateCollectionDto,
|
|
20
20
|
) {
|
|
21
|
-
return this.vectorService.createCollection(
|
|
21
|
+
return this.vectorService.createCollection(projectId, dto);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
@Get('collections')
|
|
25
25
|
@ApiOperation({ summary: 'List all collections' })
|
|
26
|
-
async listCollections(@Param('
|
|
27
|
-
return this.vectorService.listCollections(
|
|
26
|
+
async listCollections(@Param('projectId') projectId: string) {
|
|
27
|
+
return this.vectorService.listCollections(projectId);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
@Post('embeddings')
|
|
31
31
|
@ApiOperation({ summary: 'Create embedding from text' })
|
|
32
32
|
async createEmbedding(
|
|
33
|
-
@Param('
|
|
33
|
+
@Param('projectId') projectId: string,
|
|
34
34
|
@Body() dto: CreateEmbeddingDto,
|
|
35
35
|
@Request() req: any,
|
|
36
36
|
) {
|
|
37
|
-
return this.vectorService.createEmbedding(
|
|
37
|
+
return this.vectorService.createEmbedding(projectId, req.user.id, dto);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@Post('embeddings/batch')
|
|
41
41
|
@ApiOperation({ summary: 'Create multiple embeddings' })
|
|
42
42
|
async createEmbeddingsBatch(
|
|
43
|
-
@Param('
|
|
43
|
+
@Param('projectId') projectId: string,
|
|
44
44
|
@Body() items: CreateEmbeddingDto[],
|
|
45
45
|
@Request() req: any,
|
|
46
46
|
) {
|
|
47
|
-
return this.vectorService.createEmbeddingsBatch(
|
|
47
|
+
return this.vectorService.createEmbeddingsBatch(projectId, req.user.id, items);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
@Post('search')
|
|
51
51
|
@ApiOperation({ summary: 'Search similar vectors' })
|
|
52
52
|
async searchSimilar(
|
|
53
|
-
@Param('
|
|
53
|
+
@Param('projectId') projectId: string,
|
|
54
54
|
@Body() dto: SearchSimilarDto,
|
|
55
55
|
) {
|
|
56
|
-
return this.vectorService.searchSimilar(
|
|
56
|
+
return this.vectorService.searchSimilar(projectId, dto);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
@Get('embeddings/:id')
|
|
60
60
|
@ApiOperation({ summary: 'Get embedding by ID' })
|
|
61
61
|
async getEmbedding(
|
|
62
|
-
@Param('
|
|
62
|
+
@Param('projectId') projectId: string,
|
|
63
63
|
@Param('id') id: string,
|
|
64
64
|
) {
|
|
65
|
-
return this.vectorService.getEmbedding(
|
|
65
|
+
return this.vectorService.getEmbedding(projectId, id);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
@Post('embeddings/:id')
|
|
69
69
|
@ApiOperation({ summary: 'Update embedding metadata' })
|
|
70
70
|
async updateEmbedding(
|
|
71
|
-
@Param('
|
|
71
|
+
@Param('projectId') projectId: string,
|
|
72
72
|
@Param('id') id: string,
|
|
73
73
|
@Body() metadata: Record<string, any>,
|
|
74
74
|
) {
|
|
75
|
-
return this.vectorService.updateEmbedding(
|
|
75
|
+
return this.vectorService.updateEmbedding(projectId, id, metadata);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@Post('embeddings/:id/delete')
|
|
79
79
|
@ApiOperation({ summary: 'Delete embedding' })
|
|
80
80
|
async deleteEmbedding(
|
|
81
|
-
@Param('
|
|
81
|
+
@Param('projectId') projectId: string,
|
|
82
82
|
@Param('id') id: string,
|
|
83
83
|
) {
|
|
84
|
-
return this.vectorService.deleteEmbedding(
|
|
84
|
+
return this.vectorService.deleteEmbedding(projectId, id);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
@Post('query')
|
|
88
88
|
@ApiOperation({ summary: 'Natural language query with RAG' })
|
|
89
89
|
async query(
|
|
90
|
-
@Param('
|
|
90
|
+
@Param('projectId') projectId: string,
|
|
91
91
|
@Body() body: { query: string; collection?: string; limit?: number },
|
|
92
92
|
) {
|
|
93
|
-
return this.vectorService.query(
|
|
93
|
+
return this.vectorService.query(projectId, body.query, body.collection, body.limit);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -14,7 +14,7 @@ export class VectorService {
|
|
|
14
14
|
this.openaiApiKey = this.config.get('OPENAI_API_KEY');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async createCollection(
|
|
17
|
+
async createCollection(projectId: string, dto: CreateCollectionDto) {
|
|
18
18
|
// Create the collection table with pgvector extension
|
|
19
19
|
await this.prisma.$executeRaw`
|
|
20
20
|
CREATE TABLE IF NOT EXISTS vector_embeddings_${dto.name} (
|
|
@@ -22,7 +22,7 @@ export class VectorService {
|
|
|
22
22
|
content TEXT NOT NULL,
|
|
23
23
|
embedding vector(1536),
|
|
24
24
|
metadata JSONB DEFAULT '{}',
|
|
25
|
-
"
|
|
25
|
+
"projectId" UUID NOT NULL,
|
|
26
26
|
"createdBy" UUID NOT NULL,
|
|
27
27
|
"createdAt" TIMESTAMP DEFAULT NOW(),
|
|
28
28
|
"updatedAt" TIMESTAMP DEFAULT NOW()
|
|
@@ -42,26 +42,26 @@ export class VectorService {
|
|
|
42
42
|
description: dto.description,
|
|
43
43
|
dimension: dto.dimension || 1536,
|
|
44
44
|
embeddingModel: dto.embeddingModel || 'text-embedding-3-small',
|
|
45
|
-
|
|
45
|
+
projectId,
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
return collection;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async listCollections(
|
|
52
|
+
async listCollections(projectId: string) {
|
|
53
53
|
return this.prisma.vectorCollection.findMany({
|
|
54
|
-
where: {
|
|
54
|
+
where: { projectId },
|
|
55
55
|
orderBy: { createdAt: 'desc' },
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
async createEmbedding(
|
|
59
|
+
async createEmbedding(projectId: string, userId: string, dto: CreateEmbeddingDto) {
|
|
60
60
|
// Generate embedding using OpenAI
|
|
61
61
|
const embedding = await this.generateEmbedding(dto.content);
|
|
62
62
|
|
|
63
63
|
const collection = await this.prisma.vectorCollection.findFirst({
|
|
64
|
-
where: { id: dto.collectionId,
|
|
64
|
+
where: { id: dto.collectionId, projectId },
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
if (!collection) {
|
|
@@ -70,28 +70,28 @@ export class VectorService {
|
|
|
70
70
|
|
|
71
71
|
// Insert into collection table
|
|
72
72
|
const result = await this.prisma.$queryRaw`
|
|
73
|
-
INSERT INTO vector_embeddings_${collection.name} (content, embedding, metadata, "
|
|
74
|
-
VALUES (${dto.content}, ${`[${embedding.join(',')}]`}::vector, ${dto.metadata || '{}'}::jsonb, ${
|
|
73
|
+
INSERT INTO vector_embeddings_${collection.name} (content, embedding, metadata, "projectId", "createdBy")
|
|
74
|
+
VALUES (${dto.content}, ${`[${embedding.join(',')}]`}::vector, ${dto.metadata || '{}'}::jsonb, ${projectId}::uuid, ${userId}::uuid)
|
|
75
75
|
RETURNING id, content, metadata, "createdAt";
|
|
76
76
|
`;
|
|
77
77
|
|
|
78
78
|
return (result as { id: string; content: string; metadata: any; createdAt: Date }[])[0];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async createEmbeddingsBatch(
|
|
81
|
+
async createEmbeddingsBatch(projectId: string, userId: string, items: CreateEmbeddingDto[]) {
|
|
82
82
|
const results: any[] = [];
|
|
83
83
|
for (const item of items) {
|
|
84
|
-
const result = await this.createEmbedding(
|
|
84
|
+
const result = await this.createEmbedding(projectId, userId, item);
|
|
85
85
|
results.push(result as any);
|
|
86
86
|
}
|
|
87
87
|
return results;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async searchSimilar(
|
|
90
|
+
async searchSimilar(projectId: string, dto: SearchSimilarDto) {
|
|
91
91
|
const queryEmbedding = await this.generateEmbedding(dto.query);
|
|
92
92
|
|
|
93
93
|
const collection = await this.prisma.vectorCollection.findFirst({
|
|
94
|
-
where: { id: dto.collectionId,
|
|
94
|
+
where: { id: dto.collectionId, projectId },
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
if (!collection) {
|
|
@@ -110,7 +110,7 @@ export class VectorService {
|
|
|
110
110
|
"createdAt",
|
|
111
111
|
1 - (embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector) as similarity
|
|
112
112
|
FROM vector_embeddings_${collection.name}
|
|
113
|
-
WHERE "
|
|
113
|
+
WHERE "projectId" = ${projectId}::uuid
|
|
114
114
|
AND 1 - (embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector) > ${threshold}
|
|
115
115
|
ORDER BY embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector
|
|
116
116
|
LIMIT ${limit};
|
|
@@ -119,15 +119,15 @@ export class VectorService {
|
|
|
119
119
|
return results;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
async getEmbedding(
|
|
123
|
-
const collections = await this.listCollections(
|
|
122
|
+
async getEmbedding(projectId: string, id: string) {
|
|
123
|
+
const collections = await this.listCollections(projectId);
|
|
124
124
|
|
|
125
125
|
for (const collection of collections) {
|
|
126
126
|
try {
|
|
127
127
|
const result = await this.prisma.$queryRaw`
|
|
128
128
|
SELECT id, content, metadata, "createdAt", "updatedAt"
|
|
129
129
|
FROM vector_embeddings_${collection.name}
|
|
130
|
-
WHERE id = ${id}::uuid AND "
|
|
130
|
+
WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
|
|
131
131
|
`;
|
|
132
132
|
if ((result as any[]).length > 0) {
|
|
133
133
|
return { ...(result as any[])[0], collection: collection.name };
|
|
@@ -140,32 +140,32 @@ export class VectorService {
|
|
|
140
140
|
throw new NotFoundException('Embedding not found');
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
async updateEmbedding(
|
|
144
|
-
const embedding = await this.getEmbedding(
|
|
143
|
+
async updateEmbedding(projectId: string, id: string, metadata: Record<string, any>) {
|
|
144
|
+
const embedding = await this.getEmbedding(projectId, id);
|
|
145
145
|
|
|
146
146
|
await this.prisma.$executeRaw`
|
|
147
147
|
UPDATE vector_embeddings_${embedding.collection}
|
|
148
148
|
SET metadata = ${metadata}::jsonb, "updatedAt" = NOW()
|
|
149
|
-
WHERE id = ${id}::uuid AND "
|
|
149
|
+
WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
|
|
150
150
|
`;
|
|
151
151
|
|
|
152
|
-
return this.getEmbedding(
|
|
152
|
+
return this.getEmbedding(projectId, id);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
async deleteEmbedding(
|
|
156
|
-
const embedding = await this.getEmbedding(
|
|
155
|
+
async deleteEmbedding(projectId: string, id: string) {
|
|
156
|
+
const embedding = await this.getEmbedding(projectId, id);
|
|
157
157
|
|
|
158
158
|
await this.prisma.$executeRaw`
|
|
159
159
|
DELETE FROM vector_embeddings_${embedding.collection}
|
|
160
|
-
WHERE id = ${id}::uuid AND "
|
|
160
|
+
WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
|
|
161
161
|
`;
|
|
162
162
|
|
|
163
163
|
return { success: true };
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
async query(
|
|
166
|
+
async query(projectId: string, query: string, collectionName?: string, limit: number = 5) {
|
|
167
167
|
// RAG-style query: search similar and return context
|
|
168
|
-
let collections = await this.listCollections(
|
|
168
|
+
let collections = await this.listCollections(projectId);
|
|
169
169
|
|
|
170
170
|
if (collectionName) {
|
|
171
171
|
collections = collections.filter(c => c.name === collectionName);
|
|
@@ -174,7 +174,7 @@ export class VectorService {
|
|
|
174
174
|
const contexts: any[] = [];
|
|
175
175
|
|
|
176
176
|
for (const collection of collections) {
|
|
177
|
-
const results = await this.searchSimilar(
|
|
177
|
+
const results = await this.searchSimilar(projectId, {
|
|
178
178
|
query,
|
|
179
179
|
collectionId: collection.id,
|
|
180
180
|
limit,
|