added fast apply
if isEdit === true & Morph API Key is in .env: - Morph Fast Apply is activated.
This commit is contained in:
@@ -30,28 +30,28 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
explanation: '',
|
||||
template: ''
|
||||
};
|
||||
|
||||
|
||||
// Function to extract packages from import statements
|
||||
function extractPackagesFromCode(content: string): string[] {
|
||||
const packages: string[] = [];
|
||||
// Match ES6 imports
|
||||
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"]([^'"]+)['"]/g;
|
||||
let importMatch;
|
||||
|
||||
|
||||
while ((importMatch = importRegex.exec(content)) !== null) {
|
||||
const importPath = importMatch[1];
|
||||
// Skip relative imports and built-in React
|
||||
if (!importPath.startsWith('.') && !importPath.startsWith('/') &&
|
||||
importPath !== 'react' && importPath !== 'react-dom' &&
|
||||
!importPath.startsWith('@/')) {
|
||||
if (!importPath.startsWith('.') && !importPath.startsWith('/') &&
|
||||
importPath !== 'react' && importPath !== 'react-dom' &&
|
||||
!importPath.startsWith('@/')) {
|
||||
// Extract package name (handle scoped packages like @heroicons/react)
|
||||
const packageName = importPath.startsWith('@')
|
||||
const packageName = importPath.startsWith('@')
|
||||
? importPath.split('/').slice(0, 2).join('/')
|
||||
: importPath.split('/')[0];
|
||||
|
||||
|
||||
if (!packages.includes(packageName)) {
|
||||
packages.push(packageName);
|
||||
|
||||
|
||||
// Log important packages for debugging
|
||||
if (packageName === 'react-router-dom' || packageName.includes('router') || packageName.includes('icon')) {
|
||||
console.log(`[apply-ai-code-stream] Detected package from imports: ${packageName}`);
|
||||
@@ -59,13 +59,13 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
// Parse file sections - handle duplicates and prefer complete versions
|
||||
const fileMap = new Map<string, { content: string; isComplete: boolean }>();
|
||||
|
||||
|
||||
// First pass: Find all file declarations
|
||||
const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
|
||||
let match;
|
||||
@@ -73,10 +73,10 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
const filePath = match[1];
|
||||
const content = match[2].trim();
|
||||
const hasClosingTag = response.substring(match.index, match.index + match[0].length).includes('</file>');
|
||||
|
||||
|
||||
// Check if this file already exists in our map
|
||||
const existing = fileMap.get(filePath);
|
||||
|
||||
|
||||
// Decide whether to keep this version
|
||||
let shouldReplace = false;
|
||||
if (!existing) {
|
||||
@@ -90,7 +90,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
} else if (!existing.isComplete && !hasClosingTag && content.length > existing.content.length) {
|
||||
shouldReplace = true; // Both incomplete, keep longer one
|
||||
}
|
||||
|
||||
|
||||
if (shouldReplace) {
|
||||
// Additional validation: reject obviously broken content
|
||||
if (content.includes('...') && !content.includes('...props') && !content.includes('...rest')) {
|
||||
@@ -104,18 +104,18 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convert map to array for sections.files
|
||||
for (const [path, { content, isComplete }] of fileMap.entries()) {
|
||||
if (!isComplete) {
|
||||
console.log(`[apply-ai-code-stream] Warning: File ${path} appears to be truncated (no closing tag)`);
|
||||
}
|
||||
|
||||
|
||||
sections.files.push({
|
||||
path,
|
||||
content
|
||||
});
|
||||
|
||||
|
||||
// Extract packages from file content
|
||||
const filePackages = extractPackagesFromCode(content);
|
||||
for (const pkg of filePackages) {
|
||||
@@ -125,7 +125,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Also parse markdown code blocks with file paths
|
||||
const markdownFileRegex = /```(?:file )?path="([^"]+)"\n([\s\S]*?)```/g;
|
||||
while ((match = markdownFileRegex.exec(response)) !== null) {
|
||||
@@ -135,7 +135,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
path: filePath,
|
||||
content: content
|
||||
});
|
||||
|
||||
|
||||
// Extract packages from file content
|
||||
const filePackages = extractPackagesFromCode(content);
|
||||
for (const pkg of filePackages) {
|
||||
@@ -145,7 +145,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse plain text format like "Generated Files: Header.jsx, index.css"
|
||||
const generatedFilesMatch = response.match(/Generated Files?:\s*([^\n]+)/i);
|
||||
if (generatedFilesMatch) {
|
||||
@@ -155,7 +155,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
.map(f => f.trim())
|
||||
.filter(f => f.endsWith('.jsx') || f.endsWith('.js') || f.endsWith('.tsx') || f.endsWith('.ts') || f.endsWith('.css') || f.endsWith('.json') || f.endsWith('.html'));
|
||||
console.log(`[apply-ai-code-stream] Detected generated files from plain text: ${filesList.join(', ')}`);
|
||||
|
||||
|
||||
// Try to extract the actual file content if it follows
|
||||
for (const fileName of filesList) {
|
||||
// Look for the file content after the file name
|
||||
@@ -171,7 +171,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
content: codeMatch[1].trim()
|
||||
});
|
||||
console.log(`[apply-ai-code-stream] Extracted content for ${filePath}`);
|
||||
|
||||
|
||||
// Extract packages from this file
|
||||
const filePackages = extractPackagesFromCode(codeMatch[1]);
|
||||
for (const pkg of filePackages) {
|
||||
@@ -184,7 +184,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Also try to parse if the response contains raw JSX/JS code blocks
|
||||
const codeBlockRegex = /```(?:jsx?|tsx?|javascript|typescript)?\n([\s\S]*?)```/g;
|
||||
while ((match = codeBlockRegex.exec(response)) !== null) {
|
||||
@@ -194,14 +194,14 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
if (fileNameMatch) {
|
||||
const fileName = fileNameMatch[1].trim();
|
||||
const filePath = fileName.includes('/') ? fileName : `src/components/${fileName}`;
|
||||
|
||||
|
||||
// Don't add duplicate files
|
||||
if (!sections.files.some(f => f.path === filePath)) {
|
||||
sections.files.push({
|
||||
path: filePath,
|
||||
content: content
|
||||
});
|
||||
|
||||
|
||||
// Extract packages
|
||||
const filePackages = extractPackagesFromCode(content);
|
||||
for (const pkg of filePackages) {
|
||||
@@ -224,7 +224,7 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
while ((match = pkgRegex.exec(response)) !== null) {
|
||||
sections.packages.push(match[1].trim());
|
||||
}
|
||||
|
||||
|
||||
// Also parse <packages> tag with multiple packages
|
||||
const packagesRegex = /<packages>([\s\S]*?)<\/packages>/;
|
||||
const packagesMatch = response.match(packagesRegex);
|
||||
@@ -264,20 +264,20 @@ function parseAIResponse(response: string): ParsedResponse {
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { response, isEdit = false, packages = [], sandboxId } = await request.json();
|
||||
|
||||
|
||||
if (!response) {
|
||||
return NextResponse.json({
|
||||
error: 'response is required'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
// Debug log the response
|
||||
console.log('[apply-ai-code-stream] Received response to parse:');
|
||||
console.log('[apply-ai-code-stream] Response length:', response.length);
|
||||
console.log('[apply-ai-code-stream] Response preview:', response.substring(0, 500));
|
||||
console.log('[apply-ai-code-stream] isEdit:', isEdit);
|
||||
console.log('[apply-ai-code-stream] packages:', packages);
|
||||
|
||||
|
||||
// Parse the AI response
|
||||
const parsed = parseAIResponse(response);
|
||||
const morphEnabled = Boolean(isEdit && process.env.MORPH_API_KEY);
|
||||
@@ -296,15 +296,15 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
console.log('[apply-ai-code-stream] Packages found:', parsed.packages);
|
||||
|
||||
|
||||
// Initialize existingFiles if not already
|
||||
if (!global.existingFiles) {
|
||||
global.existingFiles = new Set<string>();
|
||||
}
|
||||
|
||||
|
||||
// Try to get provider from sandbox manager first
|
||||
let provider = sandboxId ? sandboxManager.getProvider(sandboxId) : sandboxManager.getActiveProvider();
|
||||
|
||||
|
||||
// Fall back to global state if not found in manager
|
||||
if (!provider) {
|
||||
provider = global.activeSandboxProvider;
|
||||
@@ -313,10 +313,10 @@ export async function POST(request: NextRequest) {
|
||||
// If we have a sandboxId but no provider, try to get or create one
|
||||
if (!provider && sandboxId) {
|
||||
console.log(`[apply-ai-code-stream] No provider found for sandbox ${sandboxId}, attempting to get or create...`);
|
||||
|
||||
|
||||
try {
|
||||
provider = await sandboxManager.getOrCreateProvider(sandboxId);
|
||||
|
||||
|
||||
// If we got a new provider (not reconnected), we need to create a new sandbox
|
||||
if (!provider.getSandboxInfo()) {
|
||||
console.log(`[apply-ai-code-stream] Creating new sandbox since reconnection failed for ${sandboxId}`);
|
||||
@@ -324,7 +324,7 @@ export async function POST(request: NextRequest) {
|
||||
await provider.setupViteApp();
|
||||
sandboxManager.registerSandbox(sandboxId, provider);
|
||||
}
|
||||
|
||||
|
||||
// Update legacy global state
|
||||
global.activeSandboxProvider = provider;
|
||||
console.log(`[apply-ai-code-stream] Successfully got provider for sandbox ${sandboxId}`);
|
||||
@@ -346,7 +346,7 @@ export async function POST(request: NextRequest) {
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we still don't have a provider, create a new one
|
||||
if (!provider) {
|
||||
console.log(`[apply-ai-code-stream] No active provider found, creating new sandbox...`);
|
||||
@@ -358,7 +358,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Register with sandbox manager
|
||||
sandboxManager.registerSandbox(sandboxInfo.sandboxId, provider);
|
||||
|
||||
|
||||
// Store in legacy global state
|
||||
global.activeSandboxProvider = provider;
|
||||
global.sandboxData = {
|
||||
@@ -385,18 +385,18 @@ export async function POST(request: NextRequest) {
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a response stream for real-time updates
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new TransformStream();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
|
||||
// Function to send progress updates
|
||||
const sendProgress = async (data: any) => {
|
||||
const message = `data: ${JSON.stringify(data)}\n\n`;
|
||||
await writer.write(encoder.encode(message));
|
||||
};
|
||||
|
||||
|
||||
// Start processing in background (pass provider and request to the async function)
|
||||
(async (providerInstance, req) => {
|
||||
const results = {
|
||||
@@ -408,10 +408,10 @@ export async function POST(request: NextRequest) {
|
||||
commandsExecuted: [] as string[],
|
||||
errors: [] as string[]
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
await sendProgress({
|
||||
type: 'start',
|
||||
await sendProgress({
|
||||
type: 'start',
|
||||
message: 'Starting code application...',
|
||||
totalSteps: 3
|
||||
});
|
||||
@@ -427,75 +427,75 @@ export async function POST(request: NextRequest) {
|
||||
// Step 1: Install packages
|
||||
const packagesArray = Array.isArray(packages) ? packages : [];
|
||||
const parsedPackages = Array.isArray(parsed.packages) ? parsed.packages : [];
|
||||
|
||||
|
||||
// Combine and deduplicate packages
|
||||
const allPackages = [...packagesArray.filter(pkg => pkg && typeof pkg === 'string'), ...parsedPackages];
|
||||
|
||||
|
||||
// Use Set to remove duplicates, then filter out pre-installed packages
|
||||
const uniquePackages = [...new Set(allPackages)]
|
||||
.filter(pkg => pkg && typeof pkg === 'string' && pkg.trim() !== '') // Remove empty strings
|
||||
.filter(pkg => pkg !== 'react' && pkg !== 'react-dom'); // Filter pre-installed
|
||||
|
||||
|
||||
// Log if we found duplicates
|
||||
if (allPackages.length !== uniquePackages.length) {
|
||||
console.log(`[apply-ai-code-stream] Removed ${allPackages.length - uniquePackages.length} duplicate packages`);
|
||||
console.log(`[apply-ai-code-stream] Original packages:`, allPackages);
|
||||
console.log(`[apply-ai-code-stream] Deduplicated packages:`, uniquePackages);
|
||||
}
|
||||
|
||||
|
||||
if (uniquePackages.length > 0) {
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
step: 1,
|
||||
message: `Installing ${uniquePackages.length} packages...`,
|
||||
packages: uniquePackages
|
||||
});
|
||||
|
||||
|
||||
// Use streaming package installation
|
||||
try {
|
||||
// Construct the API URL properly for both dev and production
|
||||
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
||||
const host = req.headers.get('host') || 'localhost:3000';
|
||||
const apiUrl = `${protocol}://${host}/api/install-packages`;
|
||||
|
||||
|
||||
const installResponse = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
packages: uniquePackages,
|
||||
sandboxId: sandboxId || providerInstance.getSandboxInfo()?.sandboxId
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
if (installResponse.ok && installResponse.body) {
|
||||
const reader = installResponse.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
if (!chunk) continue;
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6));
|
||||
|
||||
|
||||
// Forward package installation progress
|
||||
await sendProgress({
|
||||
type: 'package-progress',
|
||||
...data
|
||||
});
|
||||
|
||||
|
||||
// Track results
|
||||
if (data.type === 'success' && data.installedPackages) {
|
||||
results.packagesInstalled = data.installedPackages;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.debug('Error parsing terminal output:', parseError);
|
||||
console.debug('Error parsing terminal output:', parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,21 +510,21 @@ export async function POST(request: NextRequest) {
|
||||
results.errors.push(`Package installation failed: ${(error as Error).message}`);
|
||||
}
|
||||
} else {
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
step: 1,
|
||||
message: 'No additional packages to install, skipping...'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Step 2: Create/update files
|
||||
const filesArray = Array.isArray(parsed.files) ? parsed.files : [];
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
step: 2,
|
||||
message: `Creating ${filesArray.length} files...`
|
||||
});
|
||||
|
||||
|
||||
// Filter out config files that shouldn't be created
|
||||
const configFiles = ['tailwind.config.js', 'vite.config.js', 'package.json', 'package-lock.json', 'tsconfig.json', 'postcss.config.js'];
|
||||
let filteredFiles = filesArray.filter(file => {
|
||||
@@ -592,27 +592,27 @@ export async function POST(request: NextRequest) {
|
||||
fileName: file.path,
|
||||
action: 'creating'
|
||||
});
|
||||
|
||||
|
||||
// Normalize the file path
|
||||
let normalizedPath = file.path;
|
||||
if (normalizedPath.startsWith('/')) {
|
||||
normalizedPath = normalizedPath.substring(1);
|
||||
}
|
||||
if (!normalizedPath.startsWith('src/') &&
|
||||
!normalizedPath.startsWith('public/') &&
|
||||
normalizedPath !== 'index.html' &&
|
||||
!configFiles.includes(normalizedPath.split('/').pop() || '')) {
|
||||
if (!normalizedPath.startsWith('src/') &&
|
||||
!normalizedPath.startsWith('public/') &&
|
||||
normalizedPath !== 'index.html' &&
|
||||
!configFiles.includes(normalizedPath.split('/').pop() || '')) {
|
||||
normalizedPath = 'src/' + normalizedPath;
|
||||
}
|
||||
|
||||
|
||||
const isUpdate = global.existingFiles.has(normalizedPath);
|
||||
|
||||
|
||||
// Remove any CSS imports from JSX/JS files (we're using Tailwind)
|
||||
let fileContent = file.content;
|
||||
if (file.path.endsWith('.jsx') || file.path.endsWith('.js') || file.path.endsWith('.tsx') || file.path.endsWith('.ts')) {
|
||||
fileContent = fileContent.replace(/import\s+['"]\.\/[^'"]+\.css['"];?\s*\n?/g, '');
|
||||
}
|
||||
|
||||
|
||||
// Fix common Tailwind CSS errors in CSS files
|
||||
if (file.path.endsWith('.css')) {
|
||||
// Replace shadow-3xl with shadow-2xl (shadow-3xl doesn't exist)
|
||||
@@ -621,7 +621,7 @@ export async function POST(request: NextRequest) {
|
||||
fileContent = fileContent.replace(/shadow-4xl/g, 'shadow-2xl');
|
||||
fileContent = fileContent.replace(/shadow-5xl/g, 'shadow-2xl');
|
||||
}
|
||||
|
||||
|
||||
// Create directory if needed
|
||||
const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
|
||||
if (dirPath) {
|
||||
@@ -630,7 +630,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Write the file using provider
|
||||
await providerInstance.writeFile(normalizedPath, fileContent);
|
||||
|
||||
|
||||
// Update file cache
|
||||
if (global.sandboxState?.fileCache) {
|
||||
global.sandboxState.fileCache.files[normalizedPath] = {
|
||||
@@ -638,14 +638,14 @@ export async function POST(request: NextRequest) {
|
||||
lastModified: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (isUpdate) {
|
||||
if (results.filesUpdated) results.filesUpdated.push(normalizedPath);
|
||||
} else {
|
||||
if (results.filesCreated) results.filesCreated.push(normalizedPath);
|
||||
if (global.existingFiles) global.existingFiles.add(normalizedPath);
|
||||
}
|
||||
|
||||
|
||||
await sendProgress({
|
||||
type: 'file-complete',
|
||||
fileName: normalizedPath,
|
||||
@@ -662,16 +662,16 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Step 3: Execute commands
|
||||
const commandsArray = Array.isArray(parsed.commands) ? parsed.commands : [];
|
||||
if (commandsArray.length > 0) {
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
await sendProgress({
|
||||
type: 'step',
|
||||
step: 3,
|
||||
message: `Executing ${commandsArray.length} commands...`
|
||||
});
|
||||
|
||||
|
||||
for (const [index, cmd] of commandsArray.entries()) {
|
||||
try {
|
||||
await sendProgress({
|
||||
@@ -681,14 +681,14 @@ export async function POST(request: NextRequest) {
|
||||
command: cmd,
|
||||
action: 'executing'
|
||||
});
|
||||
|
||||
|
||||
// Use provider runCommand
|
||||
const result = await providerInstance.runCommand(cmd);
|
||||
|
||||
// Get command output from provider result
|
||||
const stdout = result.stdout;
|
||||
const stderr = result.stderr;
|
||||
|
||||
|
||||
if (stdout) {
|
||||
await sendProgress({
|
||||
type: 'command-output',
|
||||
@@ -697,7 +697,7 @@ export async function POST(request: NextRequest) {
|
||||
stream: 'stdout'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (stderr) {
|
||||
await sendProgress({
|
||||
type: 'command-output',
|
||||
@@ -706,11 +706,11 @@ export async function POST(request: NextRequest) {
|
||||
stream: 'stderr'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (results.commandsExecuted) {
|
||||
results.commandsExecuted.push(cmd);
|
||||
}
|
||||
|
||||
|
||||
await sendProgress({
|
||||
type: 'command-complete',
|
||||
command: cmd,
|
||||
@@ -729,7 +729,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Send final results
|
||||
await sendProgress({
|
||||
type: 'complete',
|
||||
@@ -738,7 +738,7 @@ export async function POST(request: NextRequest) {
|
||||
structure: parsed.structure,
|
||||
message: `Successfully applied ${results.filesCreated.length} files`
|
||||
});
|
||||
|
||||
|
||||
// Track applied files in conversation state
|
||||
if (global.conversationState && results.filesCreated.length > 0) {
|
||||
const messages = global.conversationState.context.messages;
|
||||
@@ -751,7 +751,7 @@ export async function POST(request: NextRequest) {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Track applied code in project evolution
|
||||
if (global.conversationState.context.projectEvolution) {
|
||||
global.conversationState.context.projectEvolution.majorChanges.push({
|
||||
@@ -760,10 +760,10 @@ export async function POST(request: NextRequest) {
|
||||
filesAffected: results.filesCreated || []
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
global.conversationState.lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
await sendProgress({
|
||||
type: 'error',
|
||||
@@ -773,7 +773,7 @@ export async function POST(request: NextRequest) {
|
||||
await writer.close();
|
||||
}
|
||||
})(provider, request);
|
||||
|
||||
|
||||
// Return the stream
|
||||
return new Response(stream.readable, {
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user