106 lines
8.5 KiB
JavaScript
106 lines
8.5 KiB
JavaScript
// Actual command recognizer, message/report parsing and stop-readiness boundary.
|
|||
|
|
const fs = require('node:fs'), path = require('node:path');
|
||
|
|
const root = path.resolve(__dirname, '..');
|
||
|
|
const read = p => fs.readFileSync(path.join(root, p), 'utf8');
|
||
|
|
const src = read('components/application/service/conv_service/src/volc_conv.c');
|
||
|
|
const json = path.join('E:/Codex/toolchains/esp-idf-v5.5.1/components/json/cJSON');
|
||
|
|
function section(start, end) {
|
||
|
|
const a = src.indexOf(start), b = src.indexOf(end, a + start.length);
|
||
|
|
if (a < 0 || b < 0) throw new Error('Missing production section ' + start);
|
||
|
|
return src.slice(a, b);
|
||
|
|
}
|
||
|
|
process.stdout.write(read('components/application/service/conv_service/inc/call_voice_end.h').replace('#pragma once', '') + '\n' +
|
||
|
|
read('components/application/service/conv_service/src/call_voice_end.c').replace('#include "call_voice_end.h"', '') + '\n' +
|
||
|
|
fs.readFileSync(path.join(json, 'cJSON.h'), 'utf8') + '\n' +
|
||
|
|
fs.readFileSync(path.join(json, 'cJSON.c'), 'utf8').replace('#include "cJSON.h"', '') + String.raw`
|
||
|
|
#include <assert.h>
|
||
|
|
#include <stdatomic.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
typedef void *volc_engine_t;
|
||
|
|
typedef void *volc_hal_player_t;
|
||
|
|
typedef struct {int unused;} volc_message_info_t;
|
||
|
|
typedef struct {void *player_handle[1];} volc_hal_context_t;
|
||
|
|
static volc_hal_context_t hal={{(void*)1}};
|
||
|
|
static struct {void *engine;int wait_time;} conv_service;
|
||
|
|
static bool is_stop,stop_requested,session_started_reported,rtc=true,player_quiet=true;
|
||
|
|
static call_voice_end_t voice_end;
|
||
|
|
static atomic_uint voice_finish_serial,last_voice_received_ms;
|
||
|
|
static atomic_bool local_speech_active;
|
||
|
|
static uint64_t now_ms;
|
||
|
|
static cJSON *queue[8];static unsigned head,tail,turns,round_id;
|
||
|
|
static void *platform_report_queue=(void*)1;
|
||
|
|
#define pdTRUE 1
|
||
|
|
#define VOLC_HAL_PLAYER_AUDIO 0
|
||
|
|
#define VOLC_FUNCTION_CALL_EXEC 1
|
||
|
|
#define VOLC_FUNCTION_CALL_TRIGGER 2
|
||
|
|
#define LOGI(...) ((void)0)
|
||
|
|
#define LOGE(...) ((void)0)
|
||
|
|
static uint64_t volc_osal_get_time_ms(void){return now_ms;}
|
||
|
|
static bool platform_runtime_rtc(void){return rtc;}
|
||
|
|
static volc_hal_context_t *volc_get_global_hal_context(void){return &hal;}
|
||
|
|
static bool volc_hal_player_voice_quiet(void *p,unsigned ms){assert(p&&(ms==600));return player_quiet;}
|
||
|
|
static void volc_hal_player_play_data(void *p,const void *d,size_t n){(void)p;(void)d;(void)n;}
|
||
|
|
static void volc_hal_player_finish_segment(void *p){(void)p;}
|
||
|
|
static void call_idle_activity(void){}
|
||
|
|
static void platform_conversation_observe(const char *type){(void)type;}
|
||
|
|
static void __on_subtitle_message_received(const cJSON *j){(void)j;}
|
||
|
|
static const char *volc_current_user_id(void *e,bool remote){(void)e;return remote?"bot":"user";}
|
||
|
|
static int platform_conversation_turn(const char *s,const char *id,const char *text){assert(s&&id&&text);turns++;return 0;}
|
||
|
|
static int platform_conversation_started(const char *ref){assert(ref);return 0;}
|
||
|
|
static void aios_event_pub(int id,cJSON *j,void *u){(void)id;(void)u;cJSON_Delete(j);}
|
||
|
|
static int xQueueSend(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(tail-head==8)return 0;queue[tail++%8]=*p;return pdTRUE;}
|
||
|
|
static int xQueueReceive(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(head==tail)return 0;*p=queue[head++%8];return pdTRUE;}
|
||
|
|
` + section('/* Called only by the conversation task,', '\nvoid conv_ai_service_init()') + String.raw`
|
||
|
|
static void subtitle(const char *user,const char *text,bool definite,bool paragraph){
|
||
|
|
cJSON *j=cJSON_CreateObject(),*list=cJSON_AddArrayToObject(j,"data"),*line=cJSON_CreateObject();
|
||
|
|
cJSON_AddStringToObject(j,"type","subtitle");cJSON_AddItemToArray(list,line);
|
||
|
|
cJSON_AddStringToObject(line,"userId",user);cJSON_AddStringToObject(line,"text",text);
|
||
|
|
cJSON_AddNumberToObject(line,"sequence",tail);cJSON_AddBoolToObject(line,"definite",definite);cJSON_AddBoolToObject(line,"paragraph",paragraph);
|
||
|
|
cJSON_AddNumberToObject(line,"roundId",++round_id);
|
||
|
|
char *s=cJSON_PrintUnformatted(j);cJSON_Delete(j);char b[1024]="subv0000";
|
||
|
|
assert(strlen(s)+8<sizeof(b));strcpy(b+8,s);free(s);
|
||
|
|
__on_volc_message_data(NULL,b,strlen(b),NULL,NULL);
|
||
|
|
assert(!voice_end.pending); // receive callback never stops or mutates the FSM
|
||
|
|
process_platform_reports();
|
||
|
|
}
|
||
|
|
static void reset(void){call_voice_end_reset(&voice_end);memset(&voice_user_subtitle,0,sizeof(voice_user_subtitle));now_ms=1000;atomic_store(&voice_finish_serial,0);atomic_store(&last_voice_received_ms,0);atomic_store(&local_speech_active,false);is_stop=stop_requested=false;player_quiet=true;}
|
||
|
|
int main(void){
|
||
|
|
const char *yes[]={"拜拜吧","拜拜","再见了","就聊到这把","就聊到这吧","今天就聊到这里吧","好了,今天我们就先聊到这儿吧。谢谢你,拜拜!","那就先不聊了","请挂断电话","结束这次聊天吧","我先走了","我要休息了","下次再聊"};
|
||
|
|
const char *no[]={"不要拜拜","先别挂断","不要结束聊天","他说拜拜","她跟我说再见","你会说拜拜吗","拜拜是什么意思","就聊到这里吗?","\"拜拜\"","‘拜拜’","拜拜?","昨天就聊到这吧","好的拜拜之后继续聊","我还不想结束聊天","不聊了才怪","拜拜,不对,我还想聊","谢谢","今天的天气很好",NULL};
|
||
|
|
for(size_t i=0;i<sizeof(yes)/sizeof(*yes);i++)assert(call_voice_end_command(yes[i]));
|
||
|
|
for(size_t i=0;i<sizeof(no)/sizeof(*no);i++)assert(!call_voice_end_command(no[i]));
|
||
|
|
char huge[2048];memset(huge,'x',sizeof(huge));huge[sizeof(huge)-1]=0;assert(!call_voice_end_command(huge));
|
||
|
|
reset();subtitle("bot","拜拜吧",true,true);assert(!voice_end.pending);
|
||
|
|
subtitle("other","拜拜吧",true,true);assert(!voice_end.pending);
|
||
|
|
subtitle("user","拜拜吧",false,true);assert(!voice_end.pending);
|
||
|
|
const char *interim="{\"type\":\"subtitle\",\"data\":[{\"userId\":\"user\",\"sequence\":3,\"roundId\":2,\"definite\":false,\"paragraph\":false,\"text\":\"拜拜\"}]}";
|
||
|
|
for(int i=0;i<100;i++)__on_volc_message_data(NULL,interim,strlen(interim),NULL,NULL);
|
||
|
|
assert(head==tail&&!voice_end.pending); // partial floods cannot consume control slots
|
||
|
|
subtitle("user","拜拜吧",true,false);assert(!voice_end.pending);
|
||
|
|
subtitle("user","拜拜,不对,我还想聊",true,true);assert(!voice_end.pending);
|
||
|
|
cJSON *part=cJSON_CreateObject();cJSON_AddNumberToObject(part,"roundId",++round_id);cJSON_AddNumberToObject(part,"sequence",1);cJSON_AddBoolToObject(part,"definite",true);cJSON_AddBoolToObject(part,"paragraph",false);cJSON_AddStringToObject(part,"text","你跟我说一句,");
|
||
|
|
process_voice_end_rtc_user(part);assert(!voice_end.pending);
|
||
|
|
cJSON_SetNumberValue(cJSON_GetObjectItem(part,"sequence"),2);cJSON_SetBoolValue(cJSON_GetObjectItem(part,"paragraph"),true);cJSON_SetValuestring(cJSON_GetObjectItem(part,"text"),"再见");
|
||
|
|
process_voice_end_rtc_user(part);assert(!voice_end.pending);cJSON_Delete(part);
|
||
|
|
subtitle("user","就聊到这把",true,true);assert(voice_end.pending&&!voice_end_ready());
|
||
|
|
uint32_t deadline=voice_end.requested_ms;now_ms=2000;
|
||
|
|
call_voice_end_user(&voice_end,"就聊到这把",now_ms,0);assert(voice_end.requested_ms==deadline);
|
||
|
|
atomic_store(&voice_finish_serial,1);now_ms=2600;player_quiet=false;assert(!voice_end_ready());
|
||
|
|
player_quiet=true;atomic_store(&last_voice_received_ms,2500);assert(!voice_end_ready());
|
||
|
|
now_ms=3800;assert(voice_end_ready());atomic_store(&local_speech_active,true);assert(!voice_end_ready());
|
||
|
|
process_voice_end_transcript("subject","等一下,我还想问个问题",true);assert(!voice_end.pending);
|
||
|
|
reset();subtitle("user","拜拜",true,true);now_ms=12999;assert(!voice_end_ready());now_ms=13000;assert(voice_end_ready());
|
||
|
|
reset();subtitle("user","拜拜",true,true);is_stop=true;process_voice_end_transcript("subject","再见",true); // teardown cannot arm another close
|
||
|
|
call_voice_end_reset(&voice_end);process_voice_end_transcript("subject","拜拜",true);assert(!voice_end.pending);
|
||
|
|
reset();now_ms=UINT32_MAX-500;call_voice_end_user(&voice_end,"拜拜",now_ms,0);now_ms+=12000;assert(call_voice_end_due(&voice_end,(uint32_t)now_ms,0,false,false));
|
||
|
|
reset();rtc=false;
|
||
|
|
const char *msg="{\"type\":\"conversation.item.input_audio_transcription.completed\",\"event_id\":\"e1\",\"transcript\":\"拜拜吧\"}";
|
||
|
|
__on_volc_message_data(NULL,msg,strlen(msg),NULL,NULL);assert(!voice_end.pending);process_platform_reports();assert(voice_end.pending);
|
||
|
|
const char *done="{\"type\":\"response.audio.done\"}";
|
||
|
|
__on_volc_message_data(NULL,done,strlen(done),NULL,NULL);assert(atomic_load(&voice_finish_serial)==1);
|
||
|
|
now_ms+=2000;assert(voice_end_ready());
|
||
|
|
puts("PASS voice end: actual RTC/WS parsing, complete user-only commands, negation/quotes/questions, playback wait, cancel, duplicate deadline, 12s fallback, clock wrap, teardown fencing");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
`);
|