146 lines
4.4 KiB
JavaScript
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 },
|
|
});
|
|
}
|