feat: add product strategy, agent skills (3 SKILL.md), and Chrome browser extension
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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' },
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user