feat: add dynamic Robot configuration choices
This commit is contained in:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user