feat: add dynamic Robot configuration choices

This commit is contained in:
2026-08-16 00:55:06 +08:00
parent 7bef261fb8
commit fe55deed04
7 changed files with 524 additions and 10 deletions

View File

@@ -18,6 +18,7 @@ const SAFE_ERROR_MESSAGES: Record<string, string> = {
ai_hardware_credential_recovery_required: 'AI hardware credential recovery is required',
ai_hardware_credential_unavailable: 'AI hardware credential is unavailable',
ai_hardware_credential_recovery_unavailable: 'AI hardware credential recovery is not currently available',
ai_hardware_catalog_unavailable: 'AI hardware configuration options are unavailable',
xiaozhi_hardware_unavailable: 'AI hardware service is unavailable',
xiaozhi_hardware_timeout: 'AI hardware service timed out',
ai_hardware_disabled: 'AI hardware module is not enabled',
@@ -80,6 +81,36 @@ export type AiHardwareAgentConfiguration = AiHardwareAgent & {
chat_history_conf: number | null;
};
export type AiHardwareCatalogModelType =
| 'VAD'
| 'ASR'
| 'LLM'
| 'VLLM'
| 'Intent'
| 'Memory'
| 'TTS';
export type AiHardwareCatalogModel = {
model_type: AiHardwareCatalogModelType;
model_id: string;
model_name: string;
supports_function_call: boolean | null;
};
export type AiHardwareCatalogVoice = {
tts_model_id: string;
voice_id: string;
voice_name: string;
languages: string[];
is_clone: boolean;
};
export type AiHardwareConfigurationCatalog = {
schema_version: 1;
models: AiHardwareCatalogModel[];
voices: AiHardwareCatalogVoice[];
};
export const AI_HARDWARE_CLEARABLE_FIELDS = [
'system_prompt',
'lang_code',
@@ -249,6 +280,62 @@ function readOverview(value: unknown): AiHardwareOverview {
};
}
const CATALOG_MODEL_TYPES = new Set<AiHardwareCatalogModelType>([
'VAD', 'ASR', 'LLM', 'VLLM', 'Intent', 'Memory', 'TTS',
]);
function boundedString(value: unknown, maxLength: number): value is string {
return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
}
function readCatalogModel(value: unknown): AiHardwareCatalogModel {
if (!isRecord(value)
|| !hasOnlyKeys(value, ['model_type', 'model_id', 'model_name', 'supports_function_call'])
|| Object.keys(value).length !== 4
|| typeof value.model_type !== 'string'
|| !CATALOG_MODEL_TYPES.has(value.model_type as AiHardwareCatalogModelType)
|| !boundedString(value.model_id, 255)
|| !boundedString(value.model_name, 128)
|| (value.supports_function_call !== null && typeof value.supports_function_call !== 'boolean')) {
invalidPayload();
}
return value as AiHardwareCatalogModel;
}
function readCatalogVoice(value: unknown): AiHardwareCatalogVoice {
if (!isRecord(value)
|| !hasOnlyKeys(value, ['tts_model_id', 'voice_id', 'voice_name', 'languages', 'is_clone'])
|| Object.keys(value).length !== 5
|| !boundedString(value.tts_model_id, 255)
|| !boundedString(value.voice_id, 255)
|| !boundedString(value.voice_name, 128)
|| !Array.isArray(value.languages)
|| value.languages.length > 16
|| value.languages.some((language) => !boundedString(language, 50))
|| typeof value.is_clone !== 'boolean') {
invalidPayload();
}
return value as AiHardwareCatalogVoice;
}
function readConfigurationCatalog(value: unknown): AiHardwareConfigurationCatalog {
if (!isRecord(value)
|| !hasOnlyKeys(value, ['schema_version', 'models', 'voices'])
|| Object.keys(value).length !== 3
|| value.schema_version !== 1
|| !Array.isArray(value.models)
|| value.models.length > 256
|| !Array.isArray(value.voices)
|| value.voices.length > 512) {
invalidPayload();
}
return {
schema_version: 1,
models: value.models.map(readCatalogModel),
voices: value.voices.map(readCatalogVoice),
};
}
function readEnvelope(value: unknown): MainEnvelope {
if (!isRecord(value) || !hasOnlyKeys(value, [
'success', 'status', 'code', 'error', 'retryable', 'retry_after_seconds', 'operation_id', 'data', 'revision',
@@ -416,6 +503,16 @@ export async function getAiHardwareOverview(): Promise<AiHardwareOverview> {
return request('', undefined, readOverview) as Promise<AiHardwareOverview>;
}
export async function getAiHardwareConfigurationCatalog(
ttsModelId?: string,
): Promise<AiHardwareConfigurationCatalog> {
if (ttsModelId !== undefined) assertString(ttsModelId, 'tts_model_id', 255);
const query = ttsModelId === undefined
? ''
: `?tts_model_id=${encodeURIComponent(ttsModelId)}`;
return request(`/catalog${query}`, undefined, readConfigurationCatalog) as Promise<AiHardwareConfigurationCatalog>;
}
export async function recoverAiHardwareCredential(
options?: AiHardwareMutationOptions,
): Promise<AiHardwareOverview> {