(request: NextRequest, { params }: RouteContext)
| 229 | * Execute AI command |
| 230 | */ |
| 231 | export async function POST(request: NextRequest, { params }: RouteContext) { |
| 232 | try { |
| 233 | const { project_id } = await params; |
| 234 | const rawBody = await request.json().catch(() => ({})); |
| 235 | const body = (rawBody && typeof rawBody === 'object' ? rawBody : {}) as ChatActRequest & |
| 236 | Record<string, unknown>; |
| 237 | |
| 238 | const project = await getProjectById(project_id); |
| 239 | if (!project) { |
| 240 | return NextResponse.json( |
| 241 | { success: false, error: 'Project not found' }, |
| 242 | { status: 404 }, |
| 243 | ); |
| 244 | } |
| 245 | |
| 246 | const legacyBody = body as Record<string, unknown>; |
| 247 | const projectRoot = resolveProjectRoot(project_id, project.repoPath); |
| 248 | const rawInstruction = typeof body.instruction === 'string' ? body.instruction : ''; |
| 249 | const instructionWithoutLegacyPaths = rawInstruction.replace(/\n*Image #\d+ path: [^\n]+/g, '').trim(); |
| 250 | |
| 251 | const rawImages: RawImageAttachment[] = Array.isArray((body as Record<string, unknown>).images) |
| 252 | ? ((body as Record<string, unknown>).images as RawImageAttachment[]) |
| 253 | : Array.isArray(legacyBody['images']) |
| 254 | ? (legacyBody['images'] as RawImageAttachment[]) |
| 255 | : []; |
| 256 | |
| 257 | const processedImages: { name: string; path: string; url: string; publicUrl?: string }[] = []; |
| 258 | for (let index = 0; index < rawImages.length; index += 1) { |
| 259 | const normalized = await normalizeImageAttachment(project_id, projectRoot, rawImages[index], index); |
| 260 | if (normalized) { |
| 261 | processedImages.push(normalized); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | const imageLines = processedImages.map((image, idx) => `Image #${idx + 1} path: ${image.path}`); |
| 266 | const finalInstruction = [instructionWithoutLegacyPaths, imageLines.join('\n')] |
| 267 | .filter((segment) => segment && segment.trim().length > 0) |
| 268 | .join('\n\n') |
| 269 | .trim(); |
| 270 | |
| 271 | if (!finalInstruction) { |
| 272 | return NextResponse.json( |
| 273 | { success: false, error: 'instruction or images are required' }, |
| 274 | { status: 400 }, |
| 275 | ); |
| 276 | } |
| 277 | |
| 278 | const cliPreferenceRaw = |
| 279 | coerceString((body as Record<string, unknown>).cliPreference) ?? |
| 280 | coerceString(legacyBody['cli_preference']) ?? |
| 281 | project.preferredCli ?? |
| 282 | 'claude'; |
| 283 | const cliPreference = cliPreferenceRaw.toLowerCase(); |
| 284 | |
| 285 | const selectedModelRaw = |
| 286 | coerceString(body.selectedModel) ?? |
| 287 | coerceString(legacyBody['selected_model']) ?? |
| 288 | project.selectedModel ?? |
nothing calls this directly
no test coverage detected