apix/app/api/meta-chat/route.ts
Khoa.vo e69c6ba64d
Some checks are pending
CI / build (18.x) (push) Waiting to run
CI / build (20.x) (push) Waiting to run
chore: Remove Grok integration, simplify Settings UI
- Removed all Grok-related code, API routes, and services
- Removed crawl4ai service and meta-crawl client
- Simplified Settings to always show cookie inputs for Meta AI
- Hid advanced wrapper settings behind collapsible section
- Provider selection now only shows Whisk and Meta AI
- Fixed unused imports and type definitions
2026-01-07 19:21:51 +07:00

208 lines
7.6 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { MetaAIClient } from '@/lib/providers/meta-client';
/**
* Meta AI Chat API
* Uses MetaAIClient directly (GraphQL-based) for Llama 3 chat
* No external Crawl4AI service needed
*/
export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { message, history, metaCookies } = body;
if (!message) {
return NextResponse.json({ error: "Message is required" }, { status: 400 });
}
if (!metaCookies) {
return NextResponse.json(
{ error: "Meta AI cookies required. Configure in Settings." },
{ status: 401 }
);
}
console.log(`[Meta Chat] Message: "${message.substring(0, 50)}..."`);
// Use MetaAIClient to send chat message
// The Meta AI API is primarily designed for image generation,
// but we can use it for text chat by not prefixing with "Imagine"
const client = new MetaAIClient({ cookies: metaCookies });
// For chat, we need to initialize session and send via GraphQL
// Since MetaAIClient.generate() adds "Imagine" prefix for images,
// we'll create a direct chat method or adapt the prompt
// Send message directly - the response will contain text, not images
const chatPrompt = message; // Don't add "Imagine" prefix for chat
try {
// Use the internal sendPrompt mechanism via generate
// But we'll extract the text response instead of images
const response = await sendMetaChatMessage(client, chatPrompt, metaCookies);
return NextResponse.json({ response });
} catch (chatError: any) {
// If the direct approach fails, provide helpful error
throw new Error(chatError.message || "Failed to get response from Meta AI");
}
} catch (error: any) {
console.error('[Meta Chat] Error:', error);
const msg = error.message || "";
const isAuthError = msg.includes("401") || msg.includes("cookies") ||
msg.includes("expired") || msg.includes("Login");
return NextResponse.json(
{ error: error.message || 'Internal Server Error' },
{ status: isAuthError ? 401 : 500 }
);
}
}
/**
* Send a chat message to Meta AI and extract text response
*/
async function sendMetaChatMessage(client: MetaAIClient, message: string, cookies: string): Promise<string> {
const META_AI_BASE = "https://www.meta.ai";
const GRAPHQL_ENDPOINT = `${META_AI_BASE}/api/graphql/`;
// Normalize cookies from JSON array to string format
const normalizedCookies = normalizeCookies(cookies);
// First we need to get session tokens
const sessionResponse = await fetch(META_AI_BASE, {
headers: {
"Cookie": normalizedCookies,
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
}
});
const html = await sessionResponse.text();
// Extract LSD token
const lsdMatch = html.match(/\"LSD\",\[\],\{\"token\":\"([^\"]+)\"/) ||
html.match(/\"lsd\":\"([^\"]+)\"/) ||
html.match(/name=\"lsd\" value=\"([^\"]+)\"/);
const lsd = lsdMatch?.[1] || '';
// Extract access token
const tokenMatch = html.match(/\"accessToken\":\"([^\"]+)\"/);
const accessToken = tokenMatch?.[1];
if (html.includes('login_form') || html.includes('login_page')) {
throw new Error("Meta AI: Cookies expired. Please update in Settings.");
}
// Send chat message
const variables = {
message: {
text: message,
content_type: "TEXT"
},
source: "PDT_CHAT_INPUT",
external_message_id: Math.random().toString(36).substring(2) + Date.now().toString(36)
};
const body = new URLSearchParams({
fb_api_caller_class: "RelayModern",
fb_api_req_friendly_name: "useAbraSendMessageMutation",
variables: JSON.stringify(variables),
doc_id: "7783822248314888",
...(lsd && { lsd }),
});
const response = await fetch(GRAPHQL_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": normalizedCookies,
"Origin": META_AI_BASE,
"Referer": `${META_AI_BASE}/`,
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Accept": "application/json",
"Accept-Language": "en-US,en;q=0.9",
...(accessToken && { "Authorization": `OAuth ${accessToken}` })
},
body: body.toString()
});
// Get response text first
const responseText = await response.text();
// Check if response is HTML (error page)
if (responseText.trim().startsWith('<') || responseText.includes('<!DOCTYPE')) {
console.error('[Meta Chat] Received HTML instead of JSON:', responseText.substring(0, 200));
if (responseText.includes('login') || responseText.includes('checkpoint')) {
throw new Error("Meta AI: Cookies expired or account requires verification. Please update cookies in Settings.");
}
throw new Error("Meta AI: Service returned an error page. Please try again later.");
}
if (!response.ok) {
console.error('[Meta Chat] Response not OK:', response.status, responseText.substring(0, 200));
throw new Error(`Meta AI Error: ${response.status}`);
}
// Parse JSON response
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
console.error('[Meta Chat] Failed to parse JSON:', responseText.substring(0, 200));
throw new Error("Meta AI: Invalid response format. Please try again.");
}
// Extract text response from the GraphQL response
const messageData = data?.data?.node?.bot_response_message ||
data?.data?.xabraAIPreviewMessageSendMutation?.message;
// Check for GraphQL errors
if (data?.errors) {
console.error('[Meta Chat] GraphQL errors:', JSON.stringify(data.errors));
throw new Error("Meta AI: Request failed. " + (data.errors[0]?.message || "Unknown error"));
}
const textResponse = messageData?.text ||
messageData?.snippet ||
messageData?.message_text ||
"I'm sorry, I couldn't generate a response.";
return textResponse;
}
/**
* Normalize cookies from JSON array format to header string format
* Handles: [{name: "foo", value: "bar"}, ...] -> "foo=bar; ..."
*/
function normalizeCookies(cookies: string): string {
if (!cookies) return '';
try {
const trimmed = cookies.trim();
// Check if it's JSON array
if (trimmed.startsWith('[')) {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
return parsed
.map((c: any) => `${c.name}=${c.value}`)
.join('; ');
}
}
// Check if it's JSON object with multiple arrays (merged cookies)
if (trimmed.startsWith('{')) {
const parsed = JSON.parse(trimmed);
// If it's an object, iterate values
const entries = Object.entries(parsed)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
return entries;
}
} catch (e) {
// Not JSON, assume it's already a string format
}
return cookies;
}