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