Files
makelore/src/lib/agent-profile.ts
inman 22add3f01f
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
完善客户端模块与工作区能力
2026-08-13 19:51:02 +08:00

187 lines
5.6 KiB
TypeScript

import { hostApiFetch } from '@/lib/host-api';
import type { AgentSessionData } from '../../shared/agent-session';
export type AgentProfileGender = 'male' | 'female' | 'other' | null;
export type AgentProfileRead = {
display_name: string | null;
age: number | null;
gender: AgentProfileGender;
avatar_url?: string | null;
share_age_with_agents: boolean;
share_gender_with_agents: boolean;
analysis_enabled: boolean;
completed: boolean;
version: number;
updated_at: string | null;
};
export type AgentProfileUpdate = {
display_name: string;
age: number | null;
gender: AgentProfileGender;
share_age_with_agents: boolean;
share_gender_with_agents: boolean;
analysis_enabled: boolean;
version: number;
session_data?: AgentSessionData;
};
type AgentProfileEnvelope = {
success?: boolean;
status?: number;
code?: string;
profile?: AgentProfileRead;
error?: string;
detail?: unknown;
};
export type AgentAvatarUpload = {
fileName: string;
mimeType: string;
dataBase64: string;
};
type AgentAvatarEnvelope = {
success?: boolean;
status?: number;
code?: string;
avatar_url?: string | null;
profile?: Pick<AgentProfileRead, 'avatar_url'>;
error?: string;
detail?: unknown;
};
export class AgentProfileApiError extends Error {
readonly status: number;
readonly detail: unknown;
readonly code: string | null;
constructor(status: number, message: string, detail?: unknown, code?: string | null) {
super(message);
this.name = 'AgentProfileApiError';
this.status = status;
this.detail = detail;
this.code = code?.trim() || readDetailCode(detail);
}
}
export function getAgentProfileApiErrorMessage(error: AgentProfileApiError): string {
if (error.status === 401) return '登录已过期,请重新登录后再同步个人资料。';
if (error.code === 'avatar_unsupported_type') return '头像仅支持 PNG、JPEG 或 WebP 图片。';
if (error.code === 'avatar_file_too_large') return '头像文件超过服务端大小限制。';
if (error.code === 'avatar_invalid_file') return '头像文件无效或无法读取。';
if (error.code === 'avatar_storage_unavailable') return '头像存储服务暂时不可用,请稍后重试。';
if (error.status === 503) {
return readDetailMessage(error.detail)
? error.message
: '个人资料服务暂时不可用,请稍后重试。';
}
return error.message;
}
function readDetailCode(detail: unknown): string | null {
if (!detail || typeof detail !== 'object' || Array.isArray(detail)) return null;
const value = (detail as Record<string, unknown>).code;
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
function readDetailMessage(detail: unknown): string | null {
if (typeof detail === 'string' && detail.trim()) return detail.trim();
if (!detail || typeof detail !== 'object' || Array.isArray(detail)) return null;
const record = detail as Record<string, unknown>;
for (const field of ['message', 'msg', 'error_description', 'error']) {
const value = record[field];
if (typeof value === 'string' && value.trim()) return value.trim();
}
return null;
}
async function requestAgentProfile(
accessToken: string,
init: RequestInit = {},
): Promise<AgentProfileRead> {
const response = await hostApiFetch<AgentProfileEnvelope>('/api/works/user/agent-profile', {
...init,
headers: {
'X-NianCode-Access-Token': accessToken,
...init.headers,
},
});
if (!response?.success || !response.profile) {
const status = typeof response?.status === 'number' ? response.status : 502;
const detailMessage = readDetailMessage(response?.detail);
const code = response?.code?.trim() || readDetailCode(response?.detail);
throw new AgentProfileApiError(
status,
response?.error || detailMessage || `Agent Profile request failed (${status})`,
response?.detail,
code,
);
}
return response.profile;
}
export function getAgentProfile(accessToken: string): Promise<AgentProfileRead> {
return requestAgentProfile(accessToken);
}
export function putAgentProfile(
accessToken: string,
update: AgentProfileUpdate,
): Promise<AgentProfileRead> {
return requestAgentProfile(accessToken, {
method: 'PUT',
body: JSON.stringify(update),
});
}
function readAvatarUrl(response: AgentAvatarEnvelope): string | null {
const value = response.avatar_url ?? response.profile?.avatar_url;
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
async function requestAgentAvatar(
accessToken: string,
init: RequestInit = {},
): Promise<string | null> {
const response = await hostApiFetch<AgentAvatarEnvelope>('/api/works/user/avatar', {
...init,
headers: {
'X-NianCode-Access-Token': accessToken,
...init.headers,
},
});
if (!response?.success) {
const status = typeof response?.status === 'number' ? response.status : 502;
const detailMessage = readDetailMessage(response?.detail);
const code = response?.code?.trim() || readDetailCode(response?.detail);
throw new AgentProfileApiError(
status,
response?.error || detailMessage || `Agent Avatar request failed (${status})`,
response?.detail,
code,
);
}
return readAvatarUrl(response);
}
export function uploadAgentAvatar(
accessToken: string,
upload: AgentAvatarUpload,
): Promise<string | null> {
return requestAgentAvatar(accessToken, {
method: 'POST',
body: JSON.stringify(upload),
});
}
export function deleteAgentAvatar(accessToken: string): Promise<string | null> {
return requestAgentAvatar(accessToken, { method: 'DELETE' });
}