continue re-design
This commit is contained in:
@@ -59,10 +59,26 @@ export async function POST(request: NextRequest) {
|
||||
case 'clear-old':
|
||||
// Clear old conversation data but keep recent context
|
||||
if (!global.conversationState) {
|
||||
// Initialize conversation state if it doesn't exist
|
||||
global.conversationState = {
|
||||
conversationId: `conv-${Date.now()}`,
|
||||
startedAt: Date.now(),
|
||||
lastUpdated: Date.now(),
|
||||
context: {
|
||||
messages: [],
|
||||
edits: [],
|
||||
projectEvolution: { majorChanges: [] },
|
||||
userPreferences: {}
|
||||
}
|
||||
};
|
||||
|
||||
console.log('[conversation-state] Initialized new conversation state for clear-old');
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'No active conversation to clear'
|
||||
}, { status: 400 });
|
||||
success: true,
|
||||
message: 'New conversation state initialized',
|
||||
state: global.conversationState
|
||||
});
|
||||
}
|
||||
|
||||
// Keep only recent data
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { SandboxState } from '@/types/sandbox';
|
||||
|
||||
// Store active sandbox globally
|
||||
declare global {
|
||||
var activeSandboxProvider: SandboxProvider | null;
|
||||
var activeSandboxProvider: any;
|
||||
var sandboxData: any;
|
||||
var existingFiles: Set<string>;
|
||||
var sandboxState: SandboxState;
|
||||
|
||||
@@ -18,6 +18,12 @@ export const dynamic = 'force-dynamic';
|
||||
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
|
||||
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
|
||||
|
||||
console.log('[generate-ai-code-stream] AI Gateway config:', {
|
||||
isUsingAIGateway,
|
||||
hasGroqKey: !!process.env.GROQ_API_KEY,
|
||||
hasAIGatewayKey: !!process.env.AI_GATEWAY_API_KEY
|
||||
});
|
||||
|
||||
const groq = createGroq({
|
||||
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
|
||||
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
|
||||
@@ -152,10 +158,18 @@ export async function POST(request: NextRequest) {
|
||||
const stream = new TransformStream();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
// Function to send progress updates
|
||||
// Function to send progress updates with flushing
|
||||
const sendProgress = async (data: any) => {
|
||||
const message = `data: ${JSON.stringify(data)}\n\n`;
|
||||
await writer.write(encoder.encode(message));
|
||||
try {
|
||||
await writer.write(encoder.encode(message));
|
||||
// Force flush by writing a keep-alive comment
|
||||
if (data.type === 'stream' || data.type === 'conversation') {
|
||||
await writer.write(encoder.encode(': keepalive\n\n'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[generate-ai-code-stream] Error writing to stream:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Start processing in background
|
||||
@@ -1169,15 +1183,22 @@ CRITICAL: When files are provided in the context:
|
||||
// Determine which provider to use based on model
|
||||
const isAnthropic = model.startsWith('anthropic/');
|
||||
const isGoogle = model.startsWith('google/');
|
||||
const isOpenAI = model.startsWith('openai/gpt-5');
|
||||
const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : groq));
|
||||
const isOpenAI = model.startsWith('openai/');
|
||||
const isKimiGroq = model === 'moonshotai/kimi-k2-instruct-0905';
|
||||
const modelProvider = isAnthropic ? anthropic :
|
||||
(isOpenAI ? openai :
|
||||
(isGoogle ? googleGenerativeAI :
|
||||
(isKimiGroq ? groq : groq)));
|
||||
|
||||
// Fix model name transformation for different providers
|
||||
let actualModel: string;
|
||||
if (isAnthropic) {
|
||||
actualModel = model.replace('anthropic/', '');
|
||||
} else if (model === 'openai/gpt-5') {
|
||||
actualModel = 'gpt-5';
|
||||
} else if (isOpenAI) {
|
||||
actualModel = model.replace('openai/', '');
|
||||
} else if (isKimiGroq) {
|
||||
// Kimi on Groq - use full model string
|
||||
actualModel = 'moonshotai/kimi-k2-instruct-0905';
|
||||
} else if (isGoogle) {
|
||||
// Google uses specific model names - convert our naming to theirs
|
||||
actualModel = model.replace('google/', '');
|
||||
@@ -1186,6 +1207,8 @@ CRITICAL: When files are provided in the context:
|
||||
}
|
||||
|
||||
console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`);
|
||||
console.log(`[generate-ai-code-stream] AI Gateway enabled: ${isUsingAIGateway}`);
|
||||
console.log(`[generate-ai-code-stream] Model string: ${model}`);
|
||||
|
||||
// Make streaming API call with appropriate provider
|
||||
const streamOptions: any = {
|
||||
@@ -1349,6 +1372,11 @@ It's better to have 3 complete files than 10 incomplete files.`
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Debug: Log every 100 characters streamed
|
||||
if (generatedCode.length % 100 < text.length) {
|
||||
console.log(`[generate-ai-code-stream] Streamed ${generatedCode.length} chars`);
|
||||
}
|
||||
|
||||
// Check for package tags in buffered text (ONLY for edits, not initial generation)
|
||||
let lastIndex = 0;
|
||||
if (isEdit) {
|
||||
@@ -1638,12 +1666,28 @@ Provide the complete file content without any truncation. Include all necessary
|
||||
completionClient = openai;
|
||||
} else if (model.includes('claude')) {
|
||||
completionClient = anthropic;
|
||||
} else if (model === 'moonshotai/kimi-k2-instruct-0905') {
|
||||
completionClient = groq;
|
||||
} else {
|
||||
completionClient = groq;
|
||||
}
|
||||
|
||||
// Determine the correct model name for the completion
|
||||
let completionModelName: string;
|
||||
if (model === 'moonshotai/kimi-k2-instruct-0905') {
|
||||
completionModelName = 'moonshotai/kimi-k2-instruct-0905';
|
||||
} else if (model.includes('openai')) {
|
||||
completionModelName = model.replace('openai/', '');
|
||||
} else if (model.includes('anthropic')) {
|
||||
completionModelName = model.replace('anthropic/', '');
|
||||
} else if (model.includes('google')) {
|
||||
completionModelName = model.replace('google/', '');
|
||||
} else {
|
||||
completionModelName = model;
|
||||
}
|
||||
|
||||
const completionResult = await streamText({
|
||||
model: completionClient(modelMapping[model] || model),
|
||||
model: completionClient(completionModelName),
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import FirecrawlApp from '@mendable/firecrawl-js';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
@@ -8,43 +9,44 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'URL is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Use Firecrawl API to capture screenshot
|
||||
const firecrawlResponse = await fetch('https://api.firecrawl.dev/v1/scrape', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.FIRECRAWL_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url,
|
||||
formats: ['screenshot'], // Regular viewport screenshot, not full page
|
||||
waitFor: 3000, // Wait for page to fully load
|
||||
timeout: 30000,
|
||||
blockAds: true,
|
||||
actions: [
|
||||
{
|
||||
type: 'wait',
|
||||
milliseconds: 2000 // Additional wait for dynamic content
|
||||
}
|
||||
]
|
||||
})
|
||||
// Initialize Firecrawl with API key from environment
|
||||
const apiKey = process.env.FIRECRAWL_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error("FIRECRAWL_API_KEY not configured");
|
||||
return NextResponse.json({
|
||||
error: 'Firecrawl API key not configured'
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
const app = new FirecrawlApp({ apiKey });
|
||||
|
||||
// Use Firecrawl SDK to capture screenshot with the latest API
|
||||
const scrapeResult = await app.scrapeUrl(url, {
|
||||
formats: ['screenshot'], // Request screenshot format
|
||||
waitFor: 3000, // Wait for page to fully load
|
||||
timeout: 30000,
|
||||
onlyMainContent: false, // Get full page for screenshot
|
||||
actions: [
|
||||
{
|
||||
type: 'wait',
|
||||
milliseconds: 2000 // Additional wait for dynamic content
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!firecrawlResponse.ok) {
|
||||
const error = await firecrawlResponse.text();
|
||||
throw new Error(`Firecrawl API error: ${error}`);
|
||||
if (!scrapeResult.success) {
|
||||
throw new Error(scrapeResult.error || 'Failed to capture screenshot');
|
||||
}
|
||||
|
||||
const data = await firecrawlResponse.json();
|
||||
|
||||
if (!data.success || !data.data?.screenshot) {
|
||||
throw new Error('Failed to capture screenshot');
|
||||
if (!scrapeResult.data?.screenshot) {
|
||||
throw new Error('Screenshot not available in response');
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
screenshot: data.data.screenshot,
|
||||
metadata: data.data.metadata
|
||||
screenshot: scrapeResult.data.screenshot,
|
||||
metadata: scrapeResult.data.metadata || {}
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import FirecrawlApp from '@mendable/firecrawl-js';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { url, formats = ['markdown', 'html'], options = {} } = await request.json();
|
||||
|
||||
if (!url) {
|
||||
return NextResponse.json(
|
||||
{ error: "URL is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize Firecrawl with API key from environment
|
||||
const apiKey = process.env.FIRECRAWL_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error("FIRECRAWL_API_KEY not configured");
|
||||
// For demo purposes, return mock data if API key is not set
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
title: "Example Website",
|
||||
content: `This is a mock response for ${url}. Configure FIRECRAWL_API_KEY to enable real scraping.`,
|
||||
description: "A sample website",
|
||||
markdown: `# Example Website\n\nThis is mock content for demonstration purposes.`,
|
||||
html: `<h1>Example Website</h1><p>This is mock content for demonstration purposes.</p>`,
|
||||
metadata: {
|
||||
title: "Example Website",
|
||||
description: "A sample website",
|
||||
sourceURL: url,
|
||||
statusCode: 200
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const app = new FirecrawlApp({ apiKey });
|
||||
|
||||
// Scrape the website using the latest SDK patterns
|
||||
// Include screenshot if requested in formats
|
||||
const scrapeResult = await app.scrapeUrl(url, {
|
||||
formats: formats,
|
||||
onlyMainContent: options.onlyMainContent !== false, // Default to true for cleaner content
|
||||
waitFor: options.waitFor || 2000, // Wait for dynamic content
|
||||
timeout: options.timeout || 30000,
|
||||
...options // Pass through any additional options
|
||||
});
|
||||
|
||||
// Handle the response according to the latest SDK structure
|
||||
if (!scrapeResult.success) {
|
||||
throw new Error(scrapeResult.error || "Failed to scrape website");
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
title: scrapeResult.data?.metadata?.title || "Untitled",
|
||||
content: scrapeResult.data?.markdown || scrapeResult.data?.html || "",
|
||||
description: scrapeResult.data?.metadata?.description || "",
|
||||
markdown: scrapeResult.data?.markdown || "",
|
||||
html: scrapeResult.data?.html || "",
|
||||
metadata: scrapeResult.data?.metadata || {},
|
||||
screenshot: scrapeResult.data?.screenshot || null,
|
||||
links: scrapeResult.data?.links || [],
|
||||
// Include raw data for flexibility
|
||||
raw: scrapeResult.data
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error scraping website:", error);
|
||||
|
||||
// Return a more detailed error response
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Failed to scrape website",
|
||||
// Provide mock data as fallback for development
|
||||
data: {
|
||||
title: "Example Website",
|
||||
content: "This is fallback content due to an error. Please check your configuration.",
|
||||
description: "Error occurred while scraping",
|
||||
markdown: `# Error\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`,
|
||||
html: `<h1>Error</h1><p>${error instanceof Error ? error.message : 'Unknown error occurred'}</p>`,
|
||||
metadata: {
|
||||
title: "Error",
|
||||
description: "Failed to scrape website",
|
||||
statusCode: 500
|
||||
}
|
||||
}
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Add OPTIONS handler for CORS if needed
|
||||
export async function OPTIONS(request: NextRequest) {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user