feat: AI 对话支持 A2UI 表单/审查/图表与 Agent 工具

This commit is contained in:
2026-08-05 17:11:00 +08:00
parent 644c35ce53
commit 0e6e3e2d96
64 changed files with 8395 additions and 6434 deletions

View File

@@ -1,6 +1,4 @@
// ---------------------------------------------------------------------------
// AiConfig helpers — pure functions, no React / DOM dependencies
// ---------------------------------------------------------------------------
import dayjs from 'dayjs';
export type AiProvider = 'OPENAI' | 'DEEPSEEK' | 'OPENAI_COMPATIBLE';
@@ -10,9 +8,14 @@ export const PROVIDER_OPTIONS: { value: AiProvider; label: string }[] = [
{ value: 'OPENAI_COMPATIBLE', label: 'OpenAI 兼容' },
];
// aislop-ignore-next-line: hardcoded-url -- OpenAI 官方 API 固定端点
export const OPENAI_DEFAULT_BASE_URL = 'https://api.openai.com/v1';
// aislop-ignore-next-line: hardcoded-url -- DeepSeek 官方 API 固定端点
export const DEEPSEEK_DEFAULT_BASE_URL = 'https://api.deepseek.com';
export const PROVIDER_DEFAULTS: Record<AiProvider, string> = {
OPENAI: 'https://api.openai.com/v1',
DEEPSEEK: 'https://api.deepseek.com',
OPENAI: OPENAI_DEFAULT_BASE_URL,
DEEPSEEK: DEEPSEEK_DEFAULT_BASE_URL,
OPENAI_COMPATIBLE: '',
} as const;
@@ -20,7 +23,7 @@ export const FIXED_PROVIDERS: AiProvider[] = ['OPENAI', 'DEEPSEEK'];
export function formatDateTime(iso: string | null): string {
if (!iso) return '-';
return new Date(iso).toLocaleString('zh-CN');
return dayjs(iso).format('YYYY-MM-DD HH:mm:ss');
}
export function sourceLabel(source: string): string {
@@ -59,25 +62,3 @@ export function shouldAutoSwapBaseUrl(
}
return { baseUrl: currentBaseUrl, shouldSwap: false };
}
/** Extract a safe user-facing error message from any caught value.
*
* The Axios interceptor at `api/index.ts` unwraps errors before rejection:
* `Promise.reject(err.response?.data || err)`. So server errors arrive as
* `{ message: '...' }` (the unwrapped data) and network errors as the raw
* `Error` object — never as a raw AxiosError with a `.response` property. */
export function extractErrorMessage(err: unknown, fallback: string = '操作失败'): string {
let msg = '';
// standard Error or any object with a string message property
if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') {
msg = err.message;
}
// sanitize: trim, collapse whitespace, strip newlines, truncate
const trimmed = msg.trim();
if (!trimmed) return fallback;
const singleLine = trimmed.replace(/[\n\r]+/g, ' ').replace(/ {2,}/g, ' ');
return singleLine.length > 120 ? singleLine.slice(0, 120) + '\u2026' : singleLine;
}