feat: enhance translator functionality and UI

This commit is contained in:
decolua 2026-03-06 16:26:33 +07:00
parent 3b847c485f
commit d347de8092
10 changed files with 2950 additions and 565 deletions

View file

@ -9,10 +9,12 @@ export async function GET() {
const { password, ...safeSettings } = settings;
const enableRequestLogs = process.env.ENABLE_REQUEST_LOGS === "true";
const enableTranslator = process.env.ENABLE_TRANSLATOR === "true";
return NextResponse.json({
...safeSettings,
enableRequestLogs,
enableTranslator,
hasPassword: !!password
});
} catch (error) {

View file

@ -17,7 +17,10 @@ export async function GET(request) {
"2_req_source.json",
"3_req_openai.json",
"4_req_target.json",
"5_res_provider.txt"
"5_res_provider.txt",
"6_res_openai.txt",
"7_res_client.txt",
"7_res_client.json",
];
if (!allowedFiles.includes(file)) {

View file

@ -16,7 +16,10 @@ export async function POST(request) {
"2_req_source.json",
"3_req_openai.json",
"4_req_target.json",
"5_res_provider.txt"
"5_res_provider.txt",
"6_res_openai.txt",
"7_res_client.txt",
"7_res_client.json",
];
if (!allowedFiles.includes(file)) {

View file

@ -1,24 +1,18 @@
import { NextResponse } from "next/server";
import { buildProviderUrl, buildProviderHeaders } from "open-sse/services/provider.js";
import { getProviderConnections } from "@/lib/localDb.js";
import { getExecutor, refreshTokenByProvider } from "open-sse/index.js";
export async function POST(request) {
try {
const { provider, body } = await request.json();
const { provider, model, body } = await request.json();
if (!provider || !body) {
return NextResponse.json({ success: false, error: "Provider and body required" }, { status: 400 });
if (!provider || !model || !body) {
return Response.json({ success: false, error: "provider, model, and body required" }, { status: 400 });
}
// Get provider credentials from database
const connections = await getProviderConnections({ provider });
const connection = connections.find(c => c.isActive !== false);
if (!connection) {
return NextResponse.json({
success: false,
error: `No active connection found for provider: ${provider}. Available connections: ${connections.length}`
}, { status: 400 });
return Response.json({ success: false, error: `No active connection for provider: ${provider}` }, { status: 400 });
}
const credentials = {
@ -30,34 +24,26 @@ export async function POST(request) {
providerSpecificData: connection.providerSpecificData
};
// Build URL and headers using provider service
const url = buildProviderUrl(provider, body.model || "test-model", true, {
baseUrlIndex: 0,
baseUrl: connection.providerSpecificData?.baseUrl,
qwenResourceUrl: connection.providerSpecificData?.resourceUrl
});
console.log("🚀 ~ POST ~ url:", url)
const headers = buildProviderHeaders(provider, credentials, true, body);
console.log("🚀 ~ POST ~ headers:", headers)
const executor = getExecutor(provider);
const stream = body.stream !== false;
// Send request to provider
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body)
});
let { response } = await executor.execute({ model, body, stream, credentials });
// Auto-refresh token on 401/403 and retry (same as chatCore.js)
if (response.status === 401 || response.status === 403) {
const newCredentials = await refreshTokenByProvider(provider, credentials);
if (newCredentials?.accessToken || newCredentials?.copilotToken) {
Object.assign(credentials, newCredentials);
({ response } = await executor.execute({ model, body, stream, credentials }));
}
}
if (!response.ok) {
const errorText = await response.text();
console.log("🚀 ~ POST ~ errorText:", errorText)
return NextResponse.json({
success: false,
error: `Provider error: ${response.status} ${response.statusText}`,
details: errorText
}, { status: response.status });
console.error(`[Translator] Provider error ${response.status}:`, errorText.slice(0, 500));
return Response.json({ success: false, error: `Provider error: ${response.status}`, details: errorText }, { status: response.status });
}
// Return streaming response
return new Response(response.body, {
headers: {
"Content-Type": "text/event-stream",
@ -66,7 +52,7 @@ export async function POST(request) {
}
});
} catch (error) {
console.error("Error sending request:", error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
console.error("[Translator] Send error:", error);
return Response.json({ success: false, error: error.message }, { status: 500 });
}
}

View file

@ -1,84 +1,66 @@
import { NextResponse } from "next/server";
import { detectFormat, getTargetFormat, buildProviderUrl, buildProviderHeaders } from "open-sse/services/provider.js";
import { detectFormat, getTargetFormat } from "open-sse/services/provider.js";
import { translateRequest } from "open-sse/translator/index.js";
import { FORMATS } from "open-sse/translator/formats.js";
import { parseModel } from "open-sse/services/model.js";
import { getProviderConnections } from "@/lib/localDb.js";
import { getExecutor } from "open-sse/executors/index.js";
export async function POST(request) {
try {
const { step, provider, body } = await request.json();
const { step, body } = await request.json();
if (!step || !provider || !body) {
return NextResponse.json({ success: false, error: "Step, provider, and body required" }, { status: 400 });
if (!step || !body) {
return NextResponse.json({ success: false, error: "Step and body required" }, { status: 400 });
}
let result;
switch (step) {
case 1: {
// Step 1: Client → Source (detect format)
// Return format: { timestamp, endpoint, headers, body }
const actualBody = body.body || body;
const sourceFormat = detectFormat(actualBody);
result = {
timestamp: body.timestamp || new Date().toISOString(),
endpoint: body.endpoint || "/v1/messages",
headers: body.headers || {},
body: actualBody,
_detectedFormat: sourceFormat
};
break;
// Detect provider + formats from 1_req_client.json
const clientBody = body.body || body;
const { provider, model } = parseModel(clientBody.model);
const sourceFormat = detectFormat(clientBody);
const targetFormat = getTargetFormat(provider);
return NextResponse.json({ success: true, result: { provider, model, sourceFormat, targetFormat } });
}
case 2: {
// Step 2: Source → OpenAI
// Return format: { timestamp, headers: {}, body }
const actualBody = body.body || body;
const sourceFormat = detectFormat(actualBody);
const targetFormat = FORMATS.OPENAI;
const model = actualBody.model || "test-model";
const translated = translateRequest(sourceFormat, targetFormat, model, actualBody, true, null, provider);
result = {
timestamp: new Date().toISOString(),
headers: {},
body: translated
};
break;
// source → OpenAI intermediate (mirrors 3_req_openai.json)
// Translate source→openai only (half of the pipeline)
const clientBody = body.body || body;
const { provider, model } = parseModel(clientBody.model);
const sourceFormat = detectFormat(clientBody);
const stream = clientBody.stream !== false;
// translateRequest(source, OPENAI) = only the first half
const result = translateRequest(sourceFormat, FORMATS.OPENAI, model, clientBody, stream, null, provider);
delete result._toolNameMap;
return NextResponse.json({ success: true, result: { body: result } });
}
case 3: {
// Step 3: OpenAI → Target
// Return format: { timestamp, body }
const actualBody = body.body || body;
const sourceFormat = FORMATS.OPENAI;
const targetFormat = getTargetFormat(provider);
const model = actualBody.model || "test-model";
const translated = translateRequest(sourceFormat, targetFormat, model, actualBody, true, null, provider);
result = {
timestamp: new Date().toISOString(),
body: translated
};
break;
}
// OpenAI intermediate → target + build URL/headers (mirrors 4_req_target.json)
const openaiBody = body.body || body;
const provider = body.provider;
const model = body.model;
case 4: {
// Step 4: Build final request with real URL and headers
// Return format: { timestamp, url, headers, body }
const actualBody = body.body || body;
const model = actualBody.model || "test-model";
// Get provider credentials
if (!provider || !model) {
return NextResponse.json({ success: false, error: "provider and model required" }, { status: 400 });
}
const targetFormat = getTargetFormat(provider);
const stream = openaiBody.stream !== false;
// translateRequest(OPENAI, target) = second half of pipeline
const translated = translateRequest(FORMATS.OPENAI, targetFormat, model, openaiBody, stream, null, provider);
delete translated._toolNameMap;
// Build URL + headers via executor (same as chatCore → executor.execute)
const connections = await getProviderConnections({ provider });
const connection = connections.find(c => c.isActive !== false);
if (!connection) {
return NextResponse.json({
success: false,
error: `No active connection found for provider: ${provider}`
}, { status: 400 });
return NextResponse.json({ success: false, error: `No active connection for provider: ${provider}` }, { status: 400 });
}
const credentials = {
@ -90,30 +72,19 @@ export async function POST(request) {
providerSpecificData: connection.providerSpecificData
};
// Build URL and headers
const url = buildProviderUrl(provider, model, true, {
baseUrlIndex: 0,
baseUrl: connection.providerSpecificData?.baseUrl,
qwenResourceUrl: connection.providerSpecificData?.resourceUrl
});
const headers = buildProviderHeaders(provider, credentials, true, actualBody);
result = {
timestamp: new Date().toISOString(),
url: url,
headers: headers,
body: actualBody
};
break;
const executor = getExecutor(provider);
const url = executor.buildUrl(model, stream, 0, credentials);
const headers = executor.buildHeaders(credentials, stream);
const finalBody = executor.transformRequest(model, translated, stream, credentials);
return NextResponse.json({ success: true, result: { url, headers, body: finalBody } });
}
default:
return NextResponse.json({ success: false, error: "Invalid step" }, { status: 400 });
return NextResponse.json({ success: false, error: "Invalid step (1-3)" }, { status: 400 });
}
return NextResponse.json({ success: true, result });
} catch (error) {
console.error("Error translating:", error);
console.error("Error in translator:", error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}