fix: 修正AiModel接口字段名model_id改为id
parent
4fbb15f3d0
commit
d00ef10e9f
622
src/api.ts
622
src/api.ts
|
|
@ -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)));
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue