629 lines
22 KiB
JavaScript
629 lines
22 KiB
JavaScript
// src/api.js
|
|
// 프론트와 API가 동일 도메인(nginx 프록시)이므로 항상 상대 경로 사용.
|
|
// 절대 URL(VITE_API_BASE)은 Mixed Content를 유발하므로 사용하지 않음.
|
|
const toApiUrl = (path) => path;
|
|
|
|
export async function apiGet(path) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
headers: { "Accept": "application/json" },
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiDelete(path) {
|
|
const res = await fetch(toApiUrl(path), { method: "DELETE" });
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiPost(path, body) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
method: "POST",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
...(body ? { "Content-Type": "application/json" } : {}),
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiPut(path, body) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
method: "PUT",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
...(body ? { "Content-Type": "application/json" } : {}),
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export function getLatest() {
|
|
return apiGet("/api/lotto/latest");
|
|
}
|
|
|
|
export function getStats() {
|
|
return apiGet("/api/lotto/stats");
|
|
}
|
|
|
|
export function recommend(params) {
|
|
const qs = new URLSearchParams({
|
|
recent_window: String(params.recent_window),
|
|
recent_weight: String(params.recent_weight),
|
|
avoid_recent_k: String(params.avoid_recent_k),
|
|
});
|
|
return apiGet(`/api/lotto/recommend?${qs.toString()}`);
|
|
}
|
|
|
|
export function getHistory(limit = 30, offset = 0) {
|
|
return apiGet(`/api/history?limit=${limit}&offset=${offset}`);
|
|
}
|
|
|
|
export function deleteHistory(id) {
|
|
return apiDelete(`/api/history/${id}`);
|
|
}
|
|
|
|
// ── 시뮬레이션 관련 API ──────────────────────────────────────────────────────
|
|
|
|
export function getBestPicks(limit = 20) {
|
|
return apiGet(`/api/lotto/best?limit=${limit}`);
|
|
}
|
|
|
|
export function getAnalysis() {
|
|
return apiGet('/api/lotto/analysis');
|
|
}
|
|
|
|
export function triggerSimulate(nCandidates = 20000, topK = 100, bestN = 20) {
|
|
const qs = new URLSearchParams({
|
|
n_candidates: String(nCandidates),
|
|
top_k: String(topK),
|
|
best_n: String(bestN),
|
|
});
|
|
return apiPost(`/api/admin/simulate?${qs.toString()}`);
|
|
}
|
|
|
|
export function getStockNews(limit = 20, category) {
|
|
const qs = new URLSearchParams({ limit: String(limit) });
|
|
if (category) {
|
|
qs.set("category", category);
|
|
}
|
|
return apiGet(`/api/stock/news?${qs.toString()}`);
|
|
}
|
|
|
|
export function getStockIndices() {
|
|
return apiGet("/api/stock/indices");
|
|
}
|
|
|
|
export function getTradeBalance() {
|
|
return apiGet("/api/trade/balance");
|
|
}
|
|
|
|
export function createTradeOrder(payload) {
|
|
return apiPost("/api/trade/order", payload);
|
|
}
|
|
|
|
// ── 포트폴리오 (수동 입력) API ──────────────────────────────────────────────
|
|
|
|
export function getPortfolio() {
|
|
return apiGet("/api/portfolio");
|
|
}
|
|
|
|
export function addPortfolio(item) {
|
|
return apiPost("/api/portfolio", item);
|
|
}
|
|
|
|
export function updatePortfolio(id, fields) {
|
|
return apiPut(`/api/portfolio/${id}`, fields);
|
|
}
|
|
|
|
export function deletePortfolio(id) {
|
|
return apiDelete(`/api/portfolio/${id}`);
|
|
}
|
|
|
|
// ── 자산 스냅샷 API ──────────────────────────────────────────────────────────
|
|
// 장 마감 시점 총 자산을 기록하고, 기간별 추이를 조회합니다.
|
|
|
|
// GET /api/portfolio/snapshot/history?days=N
|
|
// response: { history: [{ date: "2026-03-07", total_assets: 12345678 }, ...] }
|
|
export function getAssetHistory(days = 30) {
|
|
const qs = days ? `?days=${days}` : '';
|
|
return apiGet(`/api/portfolio/snapshot/history${qs}`);
|
|
}
|
|
|
|
// POST /api/portfolio/snapshot (body 없이 호출 — 서버가 현재 total_assets 계산해서 저장)
|
|
// 또는 body: { total_assets: number } 로 직접 지정 가능
|
|
export function saveAssetSnapshot(total_assets) {
|
|
return apiPost('/api/portfolio/snapshot', total_assets != null ? { total_assets } : undefined);
|
|
}
|
|
|
|
// ── 예수금 API ───────────────────────────────────────────────────────────────
|
|
|
|
export function upsertCash(broker, cash) {
|
|
return apiPut('/api/portfolio/cash', { broker, cash });
|
|
}
|
|
|
|
export function deleteCash(broker) {
|
|
return apiDelete(`/api/portfolio/cash/${encodeURIComponent(broker)}`);
|
|
}
|
|
|
|
// ── 시장 심리 지표 API ────────────────────────────────────────────────────────
|
|
|
|
// CNN Fear & Greed Index (개발: vite proxy /ext/feargreed, 프로덕션: nginx proxy 필요)
|
|
export async function getFearAndGreed() {
|
|
const res = await fetch('/ext/feargreed', { headers: { Accept: 'application/json' } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
// Yahoo Finance chart API 공통 파서
|
|
async function fetchYahooPrice(extPath) {
|
|
const res = await fetch(extPath, { headers: { Accept: 'application/json' } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const data = await res.json();
|
|
const meta = data?.chart?.result?.[0]?.meta;
|
|
const price = meta?.regularMarketPrice;
|
|
const prevClose = meta?.previousClose ?? meta?.chartPreviousClose;
|
|
if (price == null) throw new Error('데이터 없음');
|
|
const rounded = Math.round(price * 100) / 100;
|
|
const change = prevClose != null ? Math.round((price - prevClose) * 100) / 100 : null;
|
|
const changePercent = prevClose ? Math.round(((price - prevClose) / prevClose) * 10000) / 100 : null;
|
|
return { value: rounded, change, changePercent };
|
|
}
|
|
|
|
// VIX 지수 (Yahoo Finance 공개 API)
|
|
export function getVix() { return fetchYahooPrice('/ext/vix'); }
|
|
|
|
// 미국 10년물 국채 금리 (^TNX)
|
|
export function getTreasury10Y() { return fetchYahooPrice('/ext/treasury'); }
|
|
|
|
// WTI 원유 선물 (CL=F)
|
|
export function getWTI() { return fetchYahooPrice('/ext/wti'); }
|
|
|
|
// Brent 원유 선물 (BZ=F)
|
|
export function getBrent() { return fetchYahooPrice('/ext/brent'); }
|
|
|
|
// ── TODO API ─────────────────────────────────────────────────────────────────
|
|
|
|
export function getTodos() {
|
|
return apiGet('/api/todos');
|
|
}
|
|
|
|
export function addTodo(data) {
|
|
return apiPost('/api/todos', data);
|
|
}
|
|
|
|
export function updateTodo(id, data) {
|
|
return apiPut(`/api/todos/${id}`, data);
|
|
}
|
|
|
|
export function deleteTodo(id) {
|
|
return apiDelete(`/api/todos/${id}`);
|
|
}
|
|
|
|
export function clearTodos() {
|
|
return apiDelete('/api/todos/done');
|
|
}
|
|
|
|
// ── 실현손익 내역 API ─────────────────────────────────────────────────────────
|
|
// GET /api/portfolio/sell-history?broker=X&days=N → { records: [...] }
|
|
// POST /api/portfolio/sell-history → 저장된 레코드 반환
|
|
// DELETE /api/portfolio/sell-history/:id → { ok: true }
|
|
|
|
export function getSellHistory({ broker, days } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (broker && broker !== 'ALL') qs.set('broker', broker);
|
|
if (days) qs.set('days', String(days));
|
|
const q = qs.toString();
|
|
return apiGet(`/api/portfolio/sell-history${q ? '?' + q : ''}`);
|
|
}
|
|
|
|
export function addSellHistory(record) {
|
|
return apiPost('/api/portfolio/sell-history', record);
|
|
}
|
|
|
|
export function updateSellHistory(id, record) {
|
|
return apiPut(`/api/portfolio/sell-history/${id}`, record);
|
|
}
|
|
|
|
export function deleteSellHistory(id) {
|
|
return apiDelete(`/api/portfolio/sell-history/${id}`);
|
|
}
|
|
|
|
// ── AI 음악 생성 API ──────────────────────────────────────────────────────────
|
|
|
|
// GET /api/music/providers → { providers: [{ id, name, description, features }] }
|
|
export function getMusicProviders() {
|
|
return apiGet('/api/music/providers');
|
|
}
|
|
|
|
// POST /api/music/generate
|
|
// body: { provider, genre, moods, instruments, duration_sec, bpm, key, scale, prompt, lyrics, instrumental }
|
|
// → { task_id: string, provider: string }
|
|
export function generateMusic(payload) {
|
|
return apiPost('/api/music/generate', payload);
|
|
}
|
|
|
|
// GET /api/music/status/:task_id
|
|
// → { status, progress, message, audio_url?, error?, provider?, track? }
|
|
export function getMusicStatus(taskId) {
|
|
return apiGet(`/api/music/status/${encodeURIComponent(taskId)}`);
|
|
}
|
|
|
|
// POST /api/music/lyrics body: { prompt }
|
|
// → { id, status, text } (Suno 가사 생성)
|
|
export function generateMusicLyrics(prompt) {
|
|
return apiPost('/api/music/lyrics', { prompt });
|
|
}
|
|
|
|
// GET /api/music/library
|
|
// → { tracks: [{ id, title, genre, ..., provider, lyrics, image_url, suno_id }] }
|
|
export function getMusicLibrary() {
|
|
return apiGet('/api/music/library');
|
|
}
|
|
|
|
// POST /api/music/library body: track object
|
|
// → saved track with id
|
|
export function saveMusicTrack(data) {
|
|
return apiPost('/api/music/library', data);
|
|
}
|
|
|
|
// DELETE /api/music/library/:id
|
|
// → { ok: true }
|
|
export function deleteMusicTrack(id) {
|
|
return apiDelete(`/api/music/library/${id}`);
|
|
}
|
|
|
|
// GET /api/music/models → { models: [{ id, name, max_duration, description }] }
|
|
export function getMusicModels() {
|
|
return apiGet('/api/music/models');
|
|
}
|
|
|
|
// GET /api/music/credits → { remaining, total, ... }
|
|
export function getMusicCredits() {
|
|
return apiGet('/api/music/credits');
|
|
}
|
|
|
|
// POST /api/music/extend body: { suno_id, continue_at, prompt, style, title, model }
|
|
// → { task_id, provider }
|
|
export function extendMusicTrack(payload) {
|
|
return apiPost('/api/music/extend', payload);
|
|
}
|
|
|
|
// POST /api/music/vocal-removal body: { suno_id, title }
|
|
// → { task_id, provider }
|
|
export function removeVocals(payload) {
|
|
return apiPost('/api/music/vocal-removal', payload);
|
|
}
|
|
|
|
// ── 저장된 가사 CRUD ─────────────────────────────────────────────────────────
|
|
|
|
// GET /api/music/lyrics/library → { lyrics: [{ id, title, text, prompt, created_at, updated_at }] }
|
|
export function getSavedLyrics() {
|
|
return apiGet('/api/music/lyrics/library');
|
|
}
|
|
|
|
// POST /api/music/lyrics/library body: { title, text, prompt }
|
|
export function saveLyrics(data) {
|
|
return apiPost('/api/music/lyrics/library', data);
|
|
}
|
|
|
|
// PUT /api/music/lyrics/library/:id body: { title?, text?, prompt? }
|
|
export function updateLyrics(id, data) {
|
|
return apiPut(`/api/music/lyrics/library/${id}`, data);
|
|
}
|
|
|
|
// DELETE /api/music/lyrics/library/:id
|
|
export function deleteLyrics(id) {
|
|
return apiDelete(`/api/music/lyrics/library/${id}`);
|
|
}
|
|
|
|
// ── Phase 1: 커버 이미지 ────────────────────────────────────────────────────
|
|
|
|
// POST /api/music/cover-image body: { suno_task_id, track_id }
|
|
export function generateCoverImage(payload) {
|
|
return apiPost('/api/music/cover-image', payload);
|
|
}
|
|
|
|
// ── Phase 2 API ─────────────────────────────────────────────────────────────
|
|
|
|
// POST /api/music/wav body: { suno_task_id, suno_id, track_id }
|
|
export function convertToWav(payload) {
|
|
return apiPost('/api/music/wav', payload);
|
|
}
|
|
|
|
// POST /api/music/stem-split body: { suno_task_id, suno_id, track_id }
|
|
export function splitStems(payload) {
|
|
return apiPost('/api/music/stem-split', payload);
|
|
}
|
|
|
|
// GET /api/music/timestamped-lyrics?task_id=...&suno_id=...
|
|
export function getTimestampedLyrics(taskId, sunoId) {
|
|
return apiGet(`/api/music/timestamped-lyrics?task_id=${encodeURIComponent(taskId)}&suno_id=${encodeURIComponent(sunoId)}`);
|
|
}
|
|
|
|
// POST /api/music/style-boost body: { content }
|
|
export function generateStyleBoost(content) {
|
|
return apiPost('/api/music/style-boost', { content });
|
|
}
|
|
|
|
// ── Phase 3 API ─────────────────────────────────────────────────────────────
|
|
|
|
// POST /api/music/upload-cover
|
|
export function uploadAndCover(payload) {
|
|
return apiPost('/api/music/upload-cover', payload);
|
|
}
|
|
|
|
// POST /api/music/upload-extend
|
|
export function uploadAndExtend(payload) {
|
|
return apiPost('/api/music/upload-extend', payload);
|
|
}
|
|
|
|
// POST /api/music/add-vocals
|
|
export function addVocals(payload) {
|
|
return apiPost('/api/music/add-vocals', payload);
|
|
}
|
|
|
|
// POST /api/music/add-instrumental
|
|
export function addInstrumental(payload) {
|
|
return apiPost('/api/music/add-instrumental', payload);
|
|
}
|
|
|
|
// POST /api/music/video
|
|
export function generateVideo(payload) {
|
|
return apiPost('/api/music/video', payload);
|
|
}
|
|
|
|
// ── 로또 고도화 API ────────────────────────────────────────────────────────────
|
|
|
|
// GET /api/lotto/stats/performance
|
|
export function getPerformanceStats() {
|
|
return apiGet('/api/lotto/stats/performance');
|
|
}
|
|
|
|
// GET /api/lotto/report/latest
|
|
export function getLatestReport() {
|
|
return apiGet('/api/lotto/report/latest');
|
|
}
|
|
|
|
// GET /api/lotto/report/:drw_no
|
|
export function getReport(drwNo) {
|
|
return apiGet(`/api/lotto/report/${drwNo}`);
|
|
}
|
|
|
|
// GET /api/lotto/report/history?limit=N
|
|
export function getReportHistory(limit = 10) {
|
|
return apiGet(`/api/lotto/report/history?limit=${limit}`);
|
|
}
|
|
|
|
// GET /api/lotto/analysis/personal
|
|
export function getPersonalAnalysis() {
|
|
return apiGet('/api/lotto/analysis/personal');
|
|
}
|
|
|
|
// ── 종합 추론 추천 ──────────────────────────────────────────────────────────
|
|
// GET /api/lotto/recommend/combined
|
|
export function getCombinedRecommend() {
|
|
return apiGet('/api/lotto/recommend/combined');
|
|
}
|
|
|
|
// GET /api/lotto/recommend/combined/history
|
|
export function getCombinedHistory(limit = 30) {
|
|
return apiGet(`/api/lotto/recommend/combined/history?limit=${limit}`);
|
|
}
|
|
|
|
// GET /api/lotto/purchase?draw_no=N&days=N
|
|
export function getPurchases({ draw_no, days } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (draw_no) qs.set('draw_no', String(draw_no));
|
|
if (days) qs.set('days', String(days));
|
|
const q = qs.toString();
|
|
return apiGet(`/api/lotto/purchase${q ? '?' + q : ''}`);
|
|
}
|
|
|
|
// GET /api/lotto/purchase/stats
|
|
export function getPurchaseStats() {
|
|
return apiGet('/api/lotto/purchase/stats');
|
|
}
|
|
|
|
// POST /api/lotto/purchase
|
|
export function addPurchase(data) {
|
|
return apiPost('/api/lotto/purchase', data);
|
|
}
|
|
|
|
// PUT /api/lotto/purchase/:id
|
|
export function updatePurchase(id, data) {
|
|
return apiPut(`/api/lotto/purchase/${id}`, data);
|
|
}
|
|
|
|
// DELETE /api/lotto/purchase/:id
|
|
export function deletePurchase(id) {
|
|
return apiDelete(`/api/lotto/purchase/${id}`);
|
|
}
|
|
|
|
// ── 블로그 API ────────────────────────────────────────────────────────────────
|
|
// GET /api/blog/posts → { posts: [{id, title, tags, body, date, excerpt}] }
|
|
// POST /api/blog/posts → 새 글 생성
|
|
// PUT /api/blog/posts/:id → 글 수정
|
|
// DELETE /api/blog/posts/:id → 글 삭제
|
|
|
|
export function getBlogPostsApi() {
|
|
return apiGet('/api/blog/posts');
|
|
}
|
|
|
|
export function createBlogPost(data) {
|
|
return apiPost('/api/blog/posts', data);
|
|
}
|
|
|
|
export function updateBlogPost(id, data) {
|
|
return apiPut(`/api/blog/posts/${id}`, data);
|
|
}
|
|
|
|
export function deleteBlogPost(id) {
|
|
return apiDelete(`/api/blog/posts/${id}`);
|
|
}
|
|
|
|
// ── 블로그 마케팅 API ────────────────────────────────────────────────────────
|
|
|
|
export function getBlogMarketingStatus() {
|
|
return apiGet('/api/blog-marketing/status');
|
|
}
|
|
|
|
export function startResearch(keyword) {
|
|
return apiPost('/api/blog-marketing/research', { keyword });
|
|
}
|
|
|
|
export function getResearchHistory(limit = 30) {
|
|
return apiGet(`/api/blog-marketing/research/history?limit=${limit}`);
|
|
}
|
|
|
|
export function getResearchDetail(id) {
|
|
return apiGet(`/api/blog-marketing/research/${id}`);
|
|
}
|
|
|
|
export function deleteResearch(id) {
|
|
return apiDelete(`/api/blog-marketing/research/${id}`);
|
|
}
|
|
|
|
export function getBlogMarketingTask(taskId) {
|
|
return apiGet(`/api/blog-marketing/task/${encodeURIComponent(taskId)}`);
|
|
}
|
|
|
|
export function startGenerate(keywordId) {
|
|
return apiPost('/api/blog-marketing/generate', { keyword_id: keywordId });
|
|
}
|
|
|
|
export function startReview(postId) {
|
|
return apiPost(`/api/blog-marketing/review/${postId}`);
|
|
}
|
|
|
|
export function startRegenerate(postId) {
|
|
return apiPost(`/api/blog-marketing/regenerate/${postId}`);
|
|
}
|
|
|
|
export function getBlogMarketingPosts(status, limit = 50) {
|
|
const qs = new URLSearchParams();
|
|
if (status) qs.set('status', status);
|
|
if (limit) qs.set('limit', String(limit));
|
|
const q = qs.toString();
|
|
return apiGet(`/api/blog-marketing/posts${q ? '?' + q : ''}`);
|
|
}
|
|
|
|
export function getBlogMarketingPost(id) {
|
|
return apiGet(`/api/blog-marketing/posts/${id}`);
|
|
}
|
|
|
|
export function updateBlogMarketingPost(id, data) {
|
|
return apiPut(`/api/blog-marketing/posts/${id}`, data);
|
|
}
|
|
|
|
export function deleteBlogMarketingPost(id) {
|
|
return apiDelete(`/api/blog-marketing/posts/${id}`);
|
|
}
|
|
|
|
export function publishBlogMarketingPost(id, naverUrl) {
|
|
return apiPost(`/api/blog-marketing/posts/${id}/publish`, { naver_url: naverUrl || '' });
|
|
}
|
|
|
|
export function getBlogMarketingCommissions(postId) {
|
|
const qs = postId ? `?post_id=${postId}` : '';
|
|
return apiGet(`/api/blog-marketing/commissions${qs}`);
|
|
}
|
|
|
|
export function addBlogMarketingCommission(data) {
|
|
return apiPost('/api/blog-marketing/commissions', data);
|
|
}
|
|
|
|
export function updateBlogMarketingCommission(id, data) {
|
|
return apiPut(`/api/blog-marketing/commissions/${id}`, data);
|
|
}
|
|
|
|
export function deleteBlogMarketingCommission(id) {
|
|
return apiDelete(`/api/blog-marketing/commissions/${id}`);
|
|
}
|
|
|
|
export function getBlogMarketingDashboard() {
|
|
return apiGet('/api/blog-marketing/dashboard');
|
|
}
|
|
|
|
// 마케터 단계
|
|
export function startMarket(postId) {
|
|
return apiPost(`/api/blog-marketing/market/${postId}`);
|
|
}
|
|
|
|
// 브랜드커넥트 링크 CRUD
|
|
export function getBrandLinks(params = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.post_id) qs.set('post_id', String(params.post_id));
|
|
if (params.keyword_id) qs.set('keyword_id', String(params.keyword_id));
|
|
const q = qs.toString();
|
|
return apiGet(`/api/blog-marketing/links${q ? '?' + q : ''}`);
|
|
}
|
|
|
|
export function createBrandLink(data) {
|
|
return apiPost('/api/blog-marketing/links', data);
|
|
}
|
|
|
|
export function updateBrandLink(id, data) {
|
|
return apiPut(`/api/blog-marketing/links/${id}`, data);
|
|
}
|
|
|
|
export function deleteBrandLink(id) {
|
|
return apiDelete(`/api/blog-marketing/links/${id}`);
|
|
}
|
|
|
|
// ── Agent Office ──────────────────────────────────
|
|
export const getAgents = () => apiGet('/api/agent-office/agents');
|
|
export const getAgentDetail = (id) => apiGet(`/api/agent-office/agents/${id}`);
|
|
export const updateAgentConfig = (id, body) => apiPut(`/api/agent-office/agents/${id}`, body);
|
|
export const getAgentTasks = (id, limit=20) => apiGet(`/api/agent-office/agents/${id}/tasks?limit=${limit}`);
|
|
export const getAgentLogs = (id, limit=50) => apiGet(`/api/agent-office/agents/${id}/logs?limit=${limit}`);
|
|
export const getPendingTasks = () => apiGet('/api/agent-office/tasks/pending');
|
|
export const sendAgentCommand = (agent, action, params={}) => apiPost('/api/agent-office/command', { agent, action, params });
|
|
export const approveAgentTask = (agent, task_id, approved, feedback='') => apiPost('/api/agent-office/approve', { agent, task_id, approved, feedback });
|
|
export const getAgentStates = () => apiGet('/api/agent-office/states');
|
|
export const getActivityFeed = (limit=50, offset=0) => apiGet(`/api/agent-office/activity?limit=${limit}&offset=${offset}`);
|
|
export const getAgentTokenUsage = (id, days=1) => apiGet(`/api/agent-office/agents/${id}/token-usage?days=${days}`);
|
|
|
|
// --- Lotto Briefing ---
|
|
|
|
export async function getLatestBriefing() {
|
|
const r = await fetch('/api/lotto/briefing/latest');
|
|
if (r.status === 404) return null;
|
|
if (!r.ok) throw new Error(`briefing fetch failed: ${r.status}`);
|
|
return r.json();
|
|
}
|
|
|
|
export async function getCuratorUsage(days = 30) {
|
|
const r = await fetch(`/api/lotto/curator/usage?days=${days}`);
|
|
if (!r.ok) throw new Error(`usage fetch failed: ${r.status}`);
|
|
return r.json();
|
|
}
|
|
|
|
export async function triggerLottoCurate() {
|
|
const r = await fetch('/api/agent-office/command', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ agent: 'lotto', action: 'curate_now', params: {} }),
|
|
});
|
|
if (!r.ok) throw new Error(`curate trigger failed: ${r.status}`);
|
|
return r.json();
|
|
}
|
|
|