fix: 修复 AI 回复无法送达发送者 + 重复消息 + action角色泄露 + OS环境支持

广播逻辑重构:
- AI 回复 (stream_start/response/stream_segments/multi_message/stream_end) 改用 broadcastToUser 发送给所有客户端
- 用户消息回显保持 broadcastToUserExcept 排除发送者

消息去重与角色修复:
- CacheMessage(user) 移至回复生成后,避免本轮 LLM 调用出现重复用户消息
- action 角色消息在 DB 存储时映射为 assistant,DeepSeek 等模型不支持自定义角色
- stream_end defer 机制确保错误路径也会终止客户端思考指示器

OS 完整环境支持:
- host 包重构为 HostBackend 接口 + Direct/WSL/Docker 三种后端
- 新增 os_exec/os_file/os_system 工具供 AI 在完整 Linux 环境中自由操作

其他:
- 视觉模型注入 + 图片预处理后清空 Images 避免传给 Chat 模型
- 图片 URL 相对路径→绝对 URL 转换
- DevTools 链路追踪页面 + 重启修复
- 记忆搜索模糊匹配增强
- 后台思考定时调度支持
- 管理后台页面 (模型配置/用户管理等)
- docs/api 更新广播机制说明

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:46:17 +08:00
parent aac64ed8b7
commit 91c9ee4b2d
49 changed files with 5032 additions and 299 deletions
+104
View File
@@ -0,0 +1,104 @@
// Admin API — /admin/models/*, /admin/sessions/*, /admin/clients/*
import { request, type ApiResponse } from './client';
import type {
ModelProvider,
ModelConfig,
RoutingRule,
SessionState,
ClientInfo,
HealthCheckResult,
ModelListResponse,
ProviderListResponse,
AdminSessionsResponse,
ActiveSessionsResponse,
ClientListResponse,
} from '@/types/admin';
// ========== Providers ==========
export function listProviders(): Promise<ApiResponse<ProviderListResponse>> {
return request<ProviderListResponse>('/admin/models/providers');
}
export function saveProvider(name: string, baseUrl: string, apiKey: string): Promise<ApiResponse<{ status: string; name: string }>> {
return request(`/admin/models/providers/${encodeURIComponent(name)}`, {
method: 'POST',
body: { name, base_url: baseUrl, api_key: apiKey },
});
}
export function deleteProvider(name: string): Promise<ApiResponse<{ status: string; name: string }>> {
return request(`/admin/models/providers/${encodeURIComponent(name)}`, { method: 'DELETE' });
}
export function healthCheckProvider(provider: string): Promise<ApiResponse<HealthCheckResult>> {
return request('/admin/models/health-check', { method: 'POST', body: { provider } });
}
export function fetchProviderModels(name: string, url?: string): Promise<ApiResponse<{ models: Array<{ id: string; name?: string }> }>> {
const query = url ? `?url=${encodeURIComponent(url)}` : '';
return request(`/admin/models/fetch-models/${encodeURIComponent(name)}${query}`);
}
// ========== Models ==========
export function listModels(): Promise<ApiResponse<ModelListResponse>> {
return request<ModelListResponse>('/admin/models/models');
}
export function saveModel(id: string, config: Partial<ModelConfig>): Promise<ApiResponse<{ status: string; id: string }>> {
return request(`/admin/models/models/${encodeURIComponent(id)}`, {
method: 'POST',
body: { id, ...config },
});
}
export function deleteModel(id: string): Promise<ApiResponse<{ status: string; id: string }>> {
return request(`/admin/models/models/${encodeURIComponent(id)}`, { method: 'DELETE' });
}
// ========== Routing ==========
export function listRouting(): Promise<ApiResponse<{ rules: RoutingRule[] }>> {
return request('/admin/models/routing');
}
export function saveRouting(purpose: string, fallbackChain: string[], required?: boolean): Promise<ApiResponse<{ status: string; purpose: string }>> {
return request(`/admin/models/routing/${encodeURIComponent(purpose)}`, {
method: 'POST',
body: { purpose, fallback_chain: fallbackChain, required },
});
}
export function deleteRouting(purpose: string): Promise<ApiResponse<{ status: string; purpose: string }>> {
return request(`/admin/models/routing/${encodeURIComponent(purpose)}`, { method: 'DELETE' });
}
// ========== Sessions ==========
export function listAdminSessions(): Promise<ApiResponse<AdminSessionsResponse>> {
return request<AdminSessionsResponse>('/admin/sessions');
}
export function listActiveSessions(): Promise<ApiResponse<ActiveSessionsResponse>> {
return request<ActiveSessionsResponse>('/admin/sessions/active');
}
export function getAdminSession(id: string): Promise<ApiResponse<SessionState>> {
return request<SessionState>(`/admin/sessions/${encodeURIComponent(id)}`);
}
// ========== Clients ==========
export function listClients(userId?: string): Promise<ApiResponse<ClientListResponse>> {
const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';
return request<ClientListResponse>(`/admin/clients${query}`);
}
export function setClientNote(clientId: string, note: string): Promise<ApiResponse<{ status: string; client_id: string; note: string }>> {
return request(`/admin/clients/${encodeURIComponent(clientId)}/note`, {
method: 'PUT',
body: { note },
});
}