2121#include "font_awesome_symbols.h"
2222#include "lvgl.h"
2323
24+ #include "tuya_ringbuf.h"
25+ #include "tkl_mutex.h"
26+
2427/***********************************************************
2528************************macro define************************
2629***********************************************************/
27- #define MAX_MASSAGE_NUM 20
30+ #define MAX_MASSAGE_NUM 20
31+ #define STREAM_BUFF_MAX_LEN 1024
32+ #define STREAM_TEXT_SHOW_WORD_NUM 5
33+ #define ONE_WORD_MAX_LEN 4
2834
2935/***********************************************************
3036***********************typedef define***********************
@@ -45,10 +51,23 @@ typedef struct {
4551 lv_obj_t * mute_label ;
4652} APP_UI_T ;
4753
54+ typedef struct {
55+ bool is_start ;
56+ TKL_MUTEX_HANDLE rb_mutex ;
57+ TUYA_RINGBUFF_T text_ringbuff ;
58+
59+ lv_obj_t * msg_cont ;
60+ lv_obj_t * bubble ;
61+ lv_obj_t * label ;
62+
63+ lv_timer_t * timer ;
64+ } APP_UI_STREAM_T ;
65+
4866typedef struct {
4967 APP_UI_T ui ;
5068
5169 UI_FONT_T font ;
70+ APP_UI_STREAM_T stream ;
5271
5372 lv_timer_t * notification_tm ;
5473} APP_CHATBOT_UI_T ;
@@ -129,7 +148,7 @@ int ui_init(UI_FONT_T *ui_font)
129148 lv_obj_set_style_text_font (screen , sg_ui .font .text , 0 );
130149 lv_obj_set_style_text_color (screen , lv_color_black (), 0 );
131150 lv_obj_set_scrollbar_mode (screen , LV_SCROLLBAR_MODE_OFF );
132- lv_obj_set_scroll_dir (screen , LV_DIR_NONE );
151+ lv_obj_set_scroll_dir (screen , LV_DIR_VER );
133152
134153 // Container
135154 sg_ui .ui .container = lv_obj_create (screen );
@@ -293,6 +312,201 @@ void ui_set_assistant_msg(const char *text)
293312 lv_obj_update_layout (sg_ui .ui .content );
294313}
295314
315+ static uint8_t __get_one_word_from_stream_ringbuff (APP_UI_STREAM_T * stream , char * result )
316+ {
317+ uint32_t rb_used_size = 0 , read_len = 0 ;
318+ uint8_t get_word_num = 0 , word_len = 0 ;
319+ char tmp = 0 ;
320+
321+ tkl_mutex_lock (stream -> rb_mutex );
322+ rb_used_size = tuya_ring_buff_used_size_get (stream -> text_ringbuff );
323+ tkl_mutex_unlock (stream -> rb_mutex );
324+ if (0 == rb_used_size ) {
325+ return 0 ;
326+ }
327+
328+ // get word len
329+ do {
330+ tkl_mutex_lock (stream -> rb_mutex );
331+ read_len = tuya_ring_buff_read (stream -> text_ringbuff , & tmp , 1 );
332+ tkl_mutex_unlock (stream -> rb_mutex );
333+
334+ if ((tmp & 0xC0 ) != 0x80 ) {
335+ if ((tmp & 0xE0 ) == 0xC0 ) {
336+ word_len = 2 ;
337+ } else if ((tmp & 0xF0 ) == 0xE0 ) {
338+ word_len = 3 ;
339+ } else if ((tmp & 0xF8 ) == 0xF0 ) {
340+ word_len = 4 ;
341+ } else {
342+ word_len = 1 ;
343+ }
344+ break ;
345+ }
346+
347+ tmp = 0 ;
348+ } while (read_len );
349+
350+ if (0 == word_len ) {
351+ return 0 ;
352+ }
353+
354+ // get word
355+ result [0 ] = tmp ;
356+
357+ if (word_len - 1 ) {
358+ tkl_mutex_lock (stream -> rb_mutex );
359+ tuya_ring_buff_read (stream -> text_ringbuff , & result [1 ], word_len - 1 );
360+ tkl_mutex_unlock (stream -> rb_mutex );
361+ }
362+
363+ return word_len ;
364+ }
365+
366+ static uint8_t __get_words_from_stream_ringbuff (APP_UI_STREAM_T * stream , uint8_t word_num , char * result )
367+ {
368+ uint8_t word_len = 0 , i = 0 , get_num = 0 ;
369+ uint32_t result_len = 0 ;
370+
371+ for (i = 0 ; i < word_num ; i ++ ) {
372+ word_len = __get_one_word_from_stream_ringbuff (stream , & result [result_len ]);
373+ if (0 == word_len ) {
374+ break ;
375+ }
376+ result_len += word_len ;
377+ get_num ++ ;
378+ }
379+
380+ result [result_len ] = '\0' ;
381+
382+ return get_num ;
383+ }
384+
385+ static void __stream_timer_cb (lv_timer_t * lv_timer )
386+ {
387+ uint8_t word_num = 0 ;
388+ char text [STREAM_TEXT_SHOW_WORD_NUM * ONE_WORD_MAX_LEN + 1 ] = {0 };
389+ APP_UI_STREAM_T * stream = (APP_UI_STREAM_T * )lv_timer_get_user_data (lv_timer );
390+
391+ word_num = __get_words_from_stream_ringbuff (stream , STREAM_TEXT_SHOW_WORD_NUM , text );
392+ if (0 == word_num ) {
393+ if (false == stream -> is_start ) {
394+ lv_timer_del (stream -> timer );
395+ stream -> timer = NULL ;
396+ }
397+ return ;
398+ }
399+
400+ lv_label_ins_text (stream -> label , LV_LABEL_POS_LAST , text );
401+
402+ lv_coord_t content_height = lv_obj_get_height (stream -> msg_cont );
403+ lv_coord_t height = lv_obj_get_height (sg_ui .ui .content );
404+ lv_coord_t y_position = content_height ;
405+
406+ if (content_height > height ) {
407+ lv_obj_scroll_to_y (sg_ui .ui .content , y_position , LV_ANIM_OFF );
408+ } else {
409+ lv_obj_scroll_to_view_recursive (stream -> msg_cont , LV_ANIM_OFF );
410+ }
411+
412+ lv_obj_update_layout (sg_ui .ui .content );
413+ }
414+
415+ void ui_set_assistant_msg_stream_start (void )
416+ {
417+ if (sg_ui .ui .content == NULL ) {
418+ return ;
419+ }
420+
421+ if (sg_ui .stream .timer ) {
422+ lv_timer_del (sg_ui .stream .timer );
423+ sg_ui .stream .timer = NULL ;
424+ }
425+
426+ // Check if the number of messages exceeds the limit
427+ uint32_t child_count = lv_obj_get_child_cnt (sg_ui .ui .content );
428+ if (child_count >= MAX_MASSAGE_NUM ) {
429+ lv_obj_t * first_child = lv_obj_get_child (sg_ui .ui .content , 0 );
430+ if (first_child ) {
431+ lv_obj_del (first_child );
432+ }
433+ }
434+
435+ sg_ui .stream .msg_cont = lv_obj_create (sg_ui .ui .content );
436+ lv_obj_remove_style_all (sg_ui .stream .msg_cont );
437+ lv_obj_set_size (sg_ui .stream .msg_cont , LV_PCT (100 ), LV_SIZE_CONTENT );
438+ lv_obj_set_style_pad_ver (sg_ui .stream .msg_cont , 6 , 0 );
439+ lv_obj_set_style_pad_column (sg_ui .stream .msg_cont , 10 , 0 );
440+
441+ lv_obj_t * avatar = lv_obj_create (sg_ui .stream .msg_cont );
442+ lv_obj_set_style_text_font (avatar , sg_ui .font .icon , 0 );
443+ lv_obj_add_style (avatar , & sg_ui .ui .style_avatar , 0 );
444+ lv_obj_set_size (avatar , 40 , 40 );
445+ lv_obj_align (avatar , LV_ALIGN_TOP_LEFT , 0 , 0 );
446+
447+ lv_obj_t * icon = lv_label_create (avatar );
448+ lv_label_set_text (icon , FONT_AWESOME_USER_ROBOT );
449+ lv_obj_center (icon );
450+
451+ sg_ui .stream .bubble = lv_obj_create (sg_ui .stream .msg_cont );
452+ lv_obj_set_width (sg_ui .stream .bubble , LV_PCT (75 ));
453+ lv_obj_set_height (sg_ui .stream .bubble , LV_SIZE_CONTENT );
454+ lv_obj_add_style (sg_ui .stream .bubble , & sg_ui .ui .style_ai_bubble , 0 );
455+ lv_obj_align_to (sg_ui .stream .bubble , avatar , LV_ALIGN_OUT_RIGHT_TOP , 10 , 0 );
456+
457+ lv_obj_set_scrollbar_mode (sg_ui .stream .bubble , LV_SCROLLBAR_MODE_OFF );
458+ lv_obj_set_scroll_dir (sg_ui .stream .bubble , LV_DIR_VER );
459+
460+ lv_obj_t * text_cont = lv_obj_create (sg_ui .stream .bubble );
461+ lv_obj_remove_style_all (text_cont );
462+ lv_obj_set_size (text_cont , LV_PCT (100 ), LV_SIZE_CONTENT );
463+ lv_obj_set_flex_flow (text_cont , LV_FLEX_FLOW_COLUMN );
464+
465+ sg_ui .stream .label = lv_label_create (text_cont );
466+ lv_label_set_text (sg_ui .stream .label , "" );
467+ lv_obj_set_width (sg_ui .stream .label , LV_PCT (100 ));
468+ lv_label_set_long_mode (sg_ui .stream .label , LV_LABEL_LONG_WRAP );
469+
470+ OPERATE_RET rt = OPRT_OK ;
471+ if (NULL == sg_ui .stream .text_ringbuff ) {
472+ rt = tuya_ring_buff_create (STREAM_BUFF_MAX_LEN , OVERFLOW_PSRAM_STOP_TYPE , & sg_ui .stream .text_ringbuff );
473+ if (rt != OPRT_OK ) {
474+ return ;
475+ }
476+ }
477+
478+ tuya_ring_buff_reset (sg_ui .stream .text_ringbuff );
479+
480+ if (sg_ui .stream .rb_mutex ) {
481+ rt = tkl_mutex_create_init (& sg_ui .stream .rb_mutex );
482+ if (rt != OPRT_OK ) {
483+ return ;
484+ }
485+ }
486+
487+ sg_ui .stream .timer = lv_timer_create (__stream_timer_cb , 1000 , & sg_ui .stream );
488+ if (NULL == sg_ui .stream .timer ) {
489+ return ;
490+ }
491+
492+ sg_ui .stream .is_start = true;
493+ }
494+
495+ void ui_set_assistant_msg_stream_data (const char * text )
496+ {
497+ if (false == sg_ui .stream .is_start ) {
498+ return ;
499+ }
500+
501+ tkl_mutex_lock (sg_ui .stream .rb_mutex );
502+ tuya_ring_buff_write (sg_ui .stream .text_ringbuff , text , strlen (text ));
503+ tkl_mutex_unlock (sg_ui .stream .rb_mutex );
504+ }
505+
506+ void ui_set_assistant_msg_stream_end (void )
507+ {
508+ sg_ui .stream .is_start = false;
509+ }
296510void ui_set_system_msg (const char * text )
297511{
298512 if (sg_ui .ui .content == NULL ) {
0 commit comments