fix: 修正AiModel接口字段名model_id改为id

main
sp mac bookpro 2605 2026-06-03 01:47:41 +08:00
parent 4fbb15f3d0
commit d00ef10e9f
2 changed files with 312 additions and 312 deletions

View File

@ -1,13 +1,13 @@
import axios from 'axios'; import axios from 'axios';
const API_BASE_URL = 'https://api.hoyidata.com/aura/v1'; const API_BASE_URL = 'https://api.hoyidata.com/aura/v1';
const APP_BASE = (import.meta.env.BASE_URL || '/').replace(/\/$/, ''); const APP_BASE = (import.meta.env.BASE_URL || '/').replace(/\/$/, '');
const withAppBase = (path: string) => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`; const withAppBase = (path: string) => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`;
const withApiBase = (path: string) => `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; const withApiBase = (path: string) => `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`;
const isMockAuth = () => typeof localStorage !== 'undefined' && localStorage.getItem('mock-auth') === '1'; const isMockAuth = () => typeof localStorage !== 'undefined' && localStorage.getItem('mock-auth') === '1';
export const api = axios.create({ export const api = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
timeout: 90000, timeout: 90000,
withCredentials: true // 关键:跨域请求带 cookie withCredentials: true // 关键:跨域请求带 cookie
}); });
@ -16,10 +16,10 @@ export const api = axios.create({
api.interceptors.response.use( api.interceptors.response.use(
(r) => r, (r) => r,
(err) => { (err) => {
const isLoginPage = location.pathname === '/login' || location.pathname === withAppBase('/login'); const isLoginPage = location.pathname === '/login' || location.pathname === withAppBase('/login');
if (err?.response?.status === 401 && !isLoginPage && !isMockAuth()) { if (err?.response?.status === 401 && !isLoginPage && !isMockAuth()) {
const next = encodeURIComponent(location.pathname + location.search); const next = encodeURIComponent(location.pathname + location.search);
location.href = `${withAppBase('/login')}?next=${next}`; location.href = `${withAppBase('/login')}?next=${next}`;
} }
return Promise.reject(err); return Promise.reject(err);
} }
@ -91,20 +91,20 @@ export interface ToolCallTrace {
result: any; result: any;
} }
export interface ChatMessage { export interface ChatMessage {
id: string; id: string;
role: 'user' | 'assistant'; role: 'user' | 'assistant';
content: string; content: string;
reasoning?: string | null; reasoning?: string | null;
parentId?: string | null; parentId?: string | null;
createdAt: number; createdAt: number;
meta?: { meta?: {
retrieved?: RetrievedSnippet[]; retrieved?: RetrievedSnippet[];
toolCalls?: ToolCallTrace[]; toolCalls?: ToolCallTrace[];
aborted?: boolean; aborted?: boolean;
reasoning?: string; reasoning?: string;
error?: string; error?: string;
} | null; } | null;
} }
export interface BranchInfo { export interface BranchInfo {
@ -118,18 +118,18 @@ export interface ChatHistoryResp {
branches: Record<string, BranchInfo>; branches: Record<string, BranchInfo>;
} }
export interface AiModel { export interface AiModel {
model_id: string; id: string;
model_name: string; model_name: string;
icon: string; icon: string;
model_ratio: number; model_ratio: number;
completion_ratio: number; completion_ratio: number;
} }
export const ModelAPI = { export const ModelAPI = {
list: () => api.get<{ data: AiModel[] }>('/models').then((r) => r.data.data), list: () => api.get<{ data: AiModel[] }>('/models').then((r) => r.data.data),
}; };
export const AgentAPI = { export const AgentAPI = {
list: () => api.get<Agent[]>('/agents').then((r) => r.data), list: () => api.get<Agent[]>('/agents').then((r) => r.data),
detail: (id: string) => api.get<Agent>(`/agents/${id}`).then((r) => r.data), detail: (id: string) => api.get<Agent>(`/agents/${id}`).then((r) => r.data),
@ -176,14 +176,14 @@ export const ChatAPI = {
agentId: string, agentId: string,
content: string, content: string,
sessionId?: string, sessionId?: string,
model?: string, model?: string,
imageUrls?: string[] imageUrls?: string[]
) => ) =>
api api
.post<{ user: ChatMessage; assistant: ChatMessage }>(`/chat/${agentId}/messages`, { .post<{ user: ChatMessage; assistant: ChatMessage }>(`/chat/${agentId}/messages`, {
content, content,
sessionId, sessionId,
model, model,
imageUrls imageUrls
}) })
.then((r) => r.data), .then((r) => r.data),
@ -320,7 +320,7 @@ export const SessionAPI = {
.then((r) => r.data), .then((r) => r.data),
/** 导出会话为文件并触发浏览器下载 */ /** 导出会话为文件并触发浏览器下载 */
exportSession: (agentId: string, sessionId: string, format: 'md' | 'json' = 'md') => { exportSession: (agentId: string, sessionId: string, format: 'md' | 'json' = 'md') => {
const url = withApiBase(`/agents/${agentId}/sessions/${sessionId}/export?format=${format}`); const url = withApiBase(`/agents/${agentId}/sessions/${sessionId}/export?format=${format}`);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.rel = 'noopener'; a.rel = 'noopener';
@ -473,114 +473,114 @@ export const PromptTemplateAPI = {
use: (id: string) => api.post(`/prompt-templates/${id}/use`).then((r) => r.data) use: (id: string) => api.post(`/prompt-templates/${id}/use`).then((r) => r.data)
}; };
// ============== 积分商城 (v1) ============== // ============== 积分商城 (v1) ==============
export interface PointsMallMe { export interface PointsMallMe {
points: number; points: number;
level?: string; level?: string;
totalSpentUSD?: number; totalSpentUSD?: number;
} }
export interface PointsExchangeRequest { export interface PointsExchangeRequest {
productId: string; productId: string;
recipientName: string; recipientName: string;
phone: string; phone: string;
province: string; province: string;
city: string; city: string;
district: string; district: string;
address: string; address: string;
zipCode?: string; zipCode?: string;
} }
export interface PointsExchangeResponse { export interface PointsExchangeResponse {
orderId: string; orderId: string;
pointsDeducted: number; pointsDeducted: number;
remainingPoints: number; remainingPoints: number;
} }
export interface PointsMallCategory { export interface PointsMallCategory {
id: string; id: string;
name: string; name: string;
sort: number; sort: number;
} }
export interface PointsMallAnnouncement { export interface PointsMallAnnouncement {
id: string; id: string;
title: string; title: string;
content: string; content: string;
linkUrl?: string; linkUrl?: string;
} }
export interface PointsMallBanner { export interface PointsMallBanner {
id: string; id: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
imageUrl: string; imageUrl: string;
linkUrl?: string; linkUrl?: string;
} }
export interface PointsMallPromoEntry { export interface PointsMallPromoEntry {
id: string; id: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
iconUrl?: string; iconUrl?: string;
linkUrl?: string; linkUrl?: string;
} }
export interface PointsMallProduct { export interface PointsMallProduct {
id: string; id: string;
categoryId: string; categoryId: string;
name: string; name: string;
subtitle?: string; subtitle?: string;
coverUrl: string; coverUrl: string;
pointsPrice: number; pointsPrice: number;
stock: number; stock: number;
sold: number; sold: number;
tags?: string[]; tags?: string[];
} }
export interface PointsMallOverview { export interface PointsMallOverview {
me: PointsMallMe; me: PointsMallMe;
categories: PointsMallCategory[]; categories: PointsMallCategory[];
announcements: PointsMallAnnouncement[]; announcements: PointsMallAnnouncement[];
banners: PointsMallBanner[]; banners: PointsMallBanner[];
promoEntries: PointsMallPromoEntry[]; promoEntries: PointsMallPromoEntry[];
} }
export interface PointsMallProductsResponse { export interface PointsMallProductsResponse {
page: number; page: number;
pageSize: number; pageSize: number;
total: number; total: number;
items: PointsMallProduct[]; items: PointsMallProduct[];
} }
export const PointsMallAPI = { export const PointsMallAPI = {
overview: () => api.get<PointsMallOverview>('/points-mall/overview').then((r) => r.data), overview: () => api.get<PointsMallOverview>('/points-mall/overview').then((r) => r.data),
products: (opts: { products: (opts: {
categoryId?: string; categoryId?: string;
q?: string; q?: string;
sort?: string; sort?: string;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
} = {}) => } = {}) =>
api api
.get<PointsMallProductsResponse>('/points-mall/products', { .get<PointsMallProductsResponse>('/points-mall/products', {
params: { params: {
categoryId: opts.categoryId, categoryId: opts.categoryId,
q: opts.q ?? '', q: opts.q ?? '',
sort: opts.sort ?? 'popular', sort: opts.sort ?? 'popular',
page: opts.page ?? 1, page: opts.page ?? 1,
pageSize: opts.pageSize ?? 24 pageSize: opts.pageSize ?? 24
} }
}) })
.then((r) => r.data), .then((r) => r.data),
exchange: (productId: string, shippingInfo: Omit<PointsExchangeRequest, 'productId'>) => exchange: (productId: string, shippingInfo: Omit<PointsExchangeRequest, 'productId'>) =>
api.post<PointsExchangeResponse>('/points-mall/exchange', { api.post<PointsExchangeResponse>('/points-mall/exchange', {
productId, productId,
...shippingInfo ...shippingInfo
}).then((r) => r.data) }).then((r) => r.data)
}; };
// ============== 调用统计 (v0.8 P1) ============== // ============== 调用统计 (v0.8 P1) ==============
export interface StatsOverview { export interface StatsOverview {
@ -601,45 +601,45 @@ export interface AgentStats {
daily: { day: string; count: number }[]; daily: { day: string; count: number }[];
} }
export interface AgentTokenStatsByModel { export interface AgentTokenStatsByModel {
providerKind: string; providerKind: string;
model: string; model: string;
calls: number; calls: number;
promptTokens: number; promptTokens: number;
completionTokens: number; completionTokens: number;
totalTokens: number; totalTokens: number;
costUSD: number; costUSD: number;
} }
export interface AgentTokenStatsDaily { export interface AgentTokenStatsDaily {
day: string; day: string;
calls: number; calls: number;
promptTokens: number; promptTokens: number;
completionTokens: number; completionTokens: number;
totalTokens: number; totalTokens: number;
costUSD: number; costUSD: number;
} }
export interface AgentTokenStats { export interface AgentTokenStats {
agentId: string; agentId: string;
days: number; days: number;
promptTokens: number; promptTokens: number;
completionTokens: number; completionTokens: number;
totalTokens: number; totalTokens: number;
costUSD: number; costUSD: number;
byModel: AgentTokenStatsByModel[]; byModel: AgentTokenStatsByModel[];
daily: AgentTokenStatsDaily[]; daily: AgentTokenStatsDaily[];
} }
export const StatsAPI = { export const StatsAPI = {
overview: () => api.get<StatsOverview>('/stats/overview').then((r) => r.data), overview: () => api.get<StatsOverview>('/stats/overview').then((r) => r.data),
agent: (id: string) => api.get<AgentStats>(`/stats/agents/${id}`).then((r) => r.data), agent: (id: string) => api.get<AgentStats>(`/stats/agents/${id}`).then((r) => r.data),
agentTokens: (id: string, opts: { days?: number; limit?: number } = {}) => agentTokens: (id: string, opts: { days?: number; limit?: number } = {}) =>
api api
.get<AgentTokenStats>(`/stats/agents/${id}/tokens`, { .get<AgentTokenStats>(`/stats/agents/${id}/tokens`, {
params: { days: opts.days ?? 30, limit: opts.limit ?? 10 } params: { days: opts.days ?? 30, limit: opts.limit ?? 10 }
}) })
.then((r) => r.data) .then((r) => r.data)
}; };
// ============== 流式(手写 SSE不用 EventSource因为它不能 POST ============== // ============== 流式(手写 SSE不用 EventSource因为它不能 POST ==============
@ -663,17 +663,17 @@ export interface LLMProvider {
updatedAt: number; updatedAt: number;
} }
export interface AgentAllowedLLMResponse { export interface AgentAllowedLLMResponse {
agentId: string; agentId: string;
model: string; model: string;
} }
export const LLMProviderAPI = { export const LLMProviderAPI = {
list: () => api.get<LLMProvider[]>('/llm-providers').then((r) => r.data), list: () => api.get<LLMProvider[]>('/llm-providers').then((r) => r.data),
allowedModels: (agentId: string) => allowedModels: (agentId: string) =>
api api
.get<AgentAllowedLLMResponse>('/llm-providers', { params: { agent_id: agentId } }) .get<AgentAllowedLLMResponse>('/llm-providers', { params: { agent_id: agentId } })
.then((r) => r.data), .then((r) => r.data),
create: (payload: Partial<LLMProvider> & { apiKey?: string }) => create: (payload: Partial<LLMProvider> & { apiKey?: string }) =>
api.post<{ id: string }>('/llm-providers', payload).then((r) => r.data), api.post<{ id: string }>('/llm-providers', payload).then((r) => r.data),
update: (id: string, payload: Partial<LLMProvider> & { apiKey?: string }) => update: (id: string, payload: Partial<LLMProvider> & { apiKey?: string }) =>
@ -689,10 +689,10 @@ export const LLMProviderAPI = {
export interface StreamEvents { export interface StreamEvents {
onMeta?: (data: any) => void; onMeta?: (data: any) => void;
onReasoningDelta?: (text: string) => void; onReasoningDelta?: (text: string) => void;
onDelta?: (text: string) => void; onDelta?: (text: string) => void;
onRetry?: (data: any) => void; onRetry?: (data: any) => void;
onToolCall?: (data: { id: string; name: string; args: any }) => void; onToolCall?: (data: { id: string; name: string; args: any }) => void;
onToolResult?: (data: { id: string; name: string; result: any }) => void; onToolResult?: (data: { id: string; name: string; result: any }) => void;
onDone?: (data: { user: ChatMessage; assistant: ChatMessage }) => void; onDone?: (data: { user: ChatMessage; assistant: ChatMessage }) => void;
@ -708,30 +708,30 @@ export interface ModelOverrides {
maxTokens?: number; maxTokens?: number;
} }
export async function streamChat( export async function streamChat(
agentId: string, agentId: string,
content: string, content: string,
handlers: StreamEvents, handlers: StreamEvents,
signal?: AbortSignal, signal?: AbortSignal,
sessionId?: string, sessionId?: string,
model?: string, model?: string,
modelId?: string, modelId?: string,
imageUrls?: string[] imageUrls?: string[]
) { ) {
const resp = await fetch(`https://api.hoyidata.com/aura/v1/chat/${agentId}/messages/stream`, { const resp = await fetch(`https://api.hoyidata.com/aura/v1/chat/${agentId}/messages/stream`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({ body: JSON.stringify({
content, content,
sessionId, sessionId,
model, model,
model_id: modelId, model_id: modelId,
imageUrls: imageUrls ?? [] imageUrls: imageUrls ?? []
}), }),
signal, signal,
credentials: 'include' credentials: 'include'
}); });
return await consumeSSE(resp, handlers, signal); return await consumeSSE(resp, handlers, signal);
} }
/** 重新生成(开新分支);行为同 streamChat */ /** 重新生成(开新分支);行为同 streamChat */
@ -745,7 +745,7 @@ export async function regenerateMessage(
) { ) {
const resp = await fetch(`https://api.hoyidata.com/aura/v1/chat/${agentId}/messages/${messageId}/regenerate`, { const resp = await fetch(`https://api.hoyidata.com/aura/v1/chat/${agentId}/messages/${messageId}/regenerate`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({ overrides, attachmentsText }), body: JSON.stringify({ overrides, attachmentsText }),
signal, signal,
credentials: 'include' credentials: 'include'
@ -753,94 +753,94 @@ export async function regenerateMessage(
return await consumeSSE(resp, handlers, signal); return await consumeSSE(resp, handlers, signal);
} }
async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal) { async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal) {
if (!resp.ok || !resp.body) { if (!resp.ok || !resp.body) {
const txt = await resp.text().catch(() => ''); const txt = await resp.text().catch(() => '');
h.onError?.(`HTTP ${resp.status}: ${txt}`); h.onError?.(`HTTP ${resp.status}: ${txt}`);
return; return;
} }
const reader = resp.body.getReader(); const reader = resp.body.getReader();
const decoder = new TextDecoder('utf-8'); const decoder = new TextDecoder('utf-8');
let buf = ''; let buf = '';
let hasError = false; // 标记是否已发生错误,避免后续事件继续处理 let hasError = false; // 标记是否已发生错误,避免后续事件继续处理
try { try {
while (true) { while (true) {
const { value, done } = await reader.read(); const { value, done } = await reader.read();
if (done || hasError) break; if (done || hasError) break;
buf += decoder.decode(value, { stream: true }); buf += decoder.decode(value, { stream: true });
buf = buf.replace(/\r\n/g, '\n'); buf = buf.replace(/\r\n/g, '\n');
let idx; let idx;
while ((idx = buf.indexOf('\n\n')) !== -1 && !hasError) { while ((idx = buf.indexOf('\n\n')) !== -1 && !hasError) {
const raw = buf.slice(0, idx); const raw = buf.slice(0, idx);
buf = buf.slice(idx + 2); buf = buf.slice(idx + 2);
if (!raw.trim() || raw.startsWith(':')) continue; if (!raw.trim() || raw.startsWith(':')) continue;
let event = 'message'; let event = 'message';
let dataStr = ''; let dataStr = '';
for (const line of raw.split('\n')) { for (const line of raw.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim(); if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) { else if (line.startsWith('data:')) {
let part = line.slice(5); let part = line.slice(5);
if (part.startsWith(' ')) part = part.slice(1); if (part.startsWith(' ')) part = part.slice(1);
dataStr += (dataStr ? '\n' : '') + part; dataStr += (dataStr ? '\n' : '') + part;
} }
} }
if (!dataStr) continue; if (!dataStr) continue;
let data: any; let data: any;
try { try {
data = JSON.parse(dataStr); data = JSON.parse(dataStr);
} catch { } catch {
continue; continue;
} }
switch (event) { switch (event) {
case 'meta': case 'meta':
h.onMeta?.(data); h.onMeta?.(data);
break; break;
case 'retry': case 'retry':
h.onRetry?.(data); h.onRetry?.(data);
break; break;
case 'reasoning_delta': case 'reasoning_delta':
h.onReasoningDelta?.(data.content || ''); h.onReasoningDelta?.(data.content || '');
break; break;
case 'delta': case 'delta':
h.onDelta?.(data.content || ''); h.onDelta?.(data.content || '');
break; break;
case 'tool_call': case 'tool_call':
h.onToolCall?.(data); h.onToolCall?.(data);
break; break;
case 'tool_result': case 'tool_result':
h.onToolResult?.(data); h.onToolResult?.(data);
break; break;
case 'done': case 'done':
// 只有在没有错误的情况下才处理 done 事件 // 只有在没有错误的情况下才处理 done 事件
if (!hasError) { if (!hasError) {
h.onDone?.(data); h.onDone?.(data);
} }
break; break;
case 'aborted': case 'aborted':
h.onAborted?.(data); h.onAborted?.(data);
break; break;
case 'error': case 'error':
hasError = true; hasError = true;
h.onError?.(data.message || 'stream error'); h.onError?.(data.message || 'stream error');
// 发生错误后立即停止读取 // 发生错误后立即停止读取
break; break;
} }
} }
} }
} catch (e: any) { } catch (e: any) {
if (signal?.aborted || e?.name === 'AbortError') { if (signal?.aborted || e?.name === 'AbortError') {
// 静默:本地已 abort // 静默:本地已 abort
return; return;
} }
h.onError?.(e?.message ?? String(e)); h.onError?.(e?.message ?? String(e));
} finally { } finally {
// 确保 reader 被释放 // 确保 reader 被释放
reader.cancel().catch(() => {}); reader.cancel().catch(() => {});
} }
} }
// ============== Workflow (v1.1) ============== // ============== Workflow (v1.1) ==============
@ -936,7 +936,7 @@ export function streamWorkflowRun(
} }
): () => void { ): () => void {
const qs = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : ''; const qs = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : '';
const url = withApiBase(`/workflows/${workflowId}/run-stream${qs}`); const url = withApiBase(`/workflows/${workflowId}/run-stream${qs}`);
const es = new EventSource(url, { withCredentials: true } as any); const es = new EventSource(url, { withCredentials: true } as any);
es.addEventListener('ready', (e: any) => handlers.onReady?.(JSON.parse(e.data))); es.addEventListener('ready', (e: any) => handlers.onReady?.(JSON.parse(e.data)));
es.addEventListener('step_start', (e: any) => handlers.onStepStart?.(JSON.parse(e.data))); es.addEventListener('step_start', (e: any) => handlers.onStepStart?.(JSON.parse(e.data)));

View File

@ -30,7 +30,7 @@ export default function ModelCheckboxDropdown({ value = [], onChange, models }:
const inputPrice = 2 * m.model_ratio; const inputPrice = 2 * m.model_ratio;
const outputPrice = inputPrice * m.completion_ratio; const outputPrice = inputPrice * m.completion_ratio;
return ( return (
<Checkbox key={m.model_id} value={m.model_id} className="agent-model-checkbox-item"> <Checkbox key={m.id} value={m.id} className="agent-model-checkbox-item">
<div className="agent-model-checkbox-content"> <div className="agent-model-checkbox-content">
<div className="agent-model-checkbox-meta"> <div className="agent-model-checkbox-meta">
<img <img