Files
trade-assistant/browser-extension/api/client.js
T

146 lines
4.4 KiB
JavaScript

/**
* TradeMate API Client
* Communicates with TradeMate backend from the browser extension.
* All functions are async, return parsed JSON or throw on error.
* Errors from 402/403 carry structured data for upgrade prompts.
*/
const STORAGE_KEYS = {
API_URL: 'trademate_api_url',
TOKEN: 'trademate_token',
};
export class ApiError extends Error {
constructor(message, status, data = {}) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data; // parsed response body (may contain credits_remaining)
this.isCreditError = status === 402;
}
}
async function getConfig() {
const { apiUrl, token } = await chrome.storage.local.get([STORAGE_KEYS.API_URL, STORAGE_KEYS.TOKEN]);
if (!apiUrl) throw new ApiError('请先设置 API 地址', 0);
if (!token) throw new ApiError('请先设置 API Token', 0);
return { apiUrl: apiUrl.replace(/\/+$/, ''), token };
}
async function request(path, options = {}) {
const { apiUrl, token } = await getConfig();
const url = `${apiUrl}${path}`;
const res = await fetch(url, {
method: options.method || 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!res.ok) {
let detail = `HTTP ${res.status}`;
let body = {};
try {
body = await res.json();
detail = body.detail || detail;
} catch {}
throw new ApiError(detail, res.status, body);
}
return res.json();
}
/** Login: get JWT token from username + password */
export async function login(username, password, apiUrl) {
const url = `${apiUrl.replace(/\/+$/, '')}/api/v1/auth/login`;
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) {
let detail = '登录失败';
try { const e = await res.json(); detail = e.detail || detail; } catch {}
throw new ApiError(detail, res.status);
}
const data = await res.json();
return data.access_token;
}
/** Get current credit balance */
export async function getBalance() {
const data = await request('/api/v1/credits/balance', { method: 'GET' });
return {
balance: data.balance ?? 0,
totalPurchased: data.total_purchased ?? 0,
totalUsed: data.total_used ?? 0,
subscription: data.subscription ?? null,
freeTrialUsed: data.free_trial_used ?? false,
dailyFreeTranslateCharsLeft: data.daily_free_translate_chars_left ?? 0,
rates: data.rates ?? {},
};
}
/** Get available subscription plans */
export async function getSubscriptionPlans() {
return request('/api/v1/credits/subscription-plans', { method: 'GET' });
}
/** Get available credit packages */
export async function getCreditPackages() {
return request('/api/v1/credits/packages', { method: 'GET' });
}
/** Translate text */
export async function translate(text, targetLang = 'zh', sourceLang = 'auto') {
return request('/api/v1/translate', {
body: { text, target_lang: targetLang, source_lang: sourceLang, context: 'trade' },
});
}
/** Generate reply suggestions */
export async function generateReply(inquiry, tone = 'professional', count = 3) {
return request('/api/v1/translate/reply', {
body: { inquiry, tone, count },
});
}
/** Search for potential customers */
export async function searchLeads(productDescription, targetMarket = 'US') {
return request('/api/v1/discovery/search', {
body: { product_description: productDescription, target_market: targetMarket },
});
}
/** Generate marketing copy */
export async function generateMarketing(productName, description, style = 'professional', count = 3) {
return request('/api/v1/marketing/generate', {
body: {
product_name: productName,
description,
style,
count,
target: 'US importers',
language: 'en',
},
});
}
/** Generate keywords */
export async function generateKeywords(productName, description, count = 10) {
return request('/api/v1/marketing/keywords', {
body: { product_name: productName, description, count, language: 'en' },
});
}
/** Extract structured customer info from pasted text */
export async function extractInfo(text, extractType = 'auto') {
return request('/api/v1/translate/extract', {
body: { text, extract_type: extractType },
});
}