bricks = window.bricks || {} bricks.ModelOutput = class extends bricks.VBox { bricks.RoleOutput = class extends bricks.VBox { /* { icon: role:'user' or 'llm', output_view: } 完成模型输出的控件的初始化以及获得数据后的更新, 更新是的数据在流模式下,需要使用累积数据 */ constructor(opts){ if(! opts){ opts = {}; } opts.width = '100%'; opts.height = 'auto'; super(opts); var hb = new bricks.HBox({width:'100%', cheight:2}); var default_icon; if (this.role == 'llm'){ default_icon = bricks_resource('imgs/llm.svg') } else { defautl_icon = bricks_resource('imgs/user.svg') } this.img = new bricks.Svg({ rate:2, tip:this.opts.model, url:this.icon || default_icon }); hb.add_widget(this.img); var mname = new bricks.Text({text:this.opts.model}); hb.add_widget(mname); this.add_widget(hb); this.content = new bricks.HBox({width:'100%'}); this.add_widget(this.content); this.logid = null; this.run = new bricks.BaseRunning({target:this}); this.content.add_widget(this.run); this.filler = new bricks.VBox({}); this.filler.set_css('filler'); if (this.role == 'llm'){ this.content.add_widget(new bricks.BlankIcon({rate:2, flexShrink:0})); } this.content.add_widget(this.filler); if (this.role != 'llm'){ this.content.add_widget(new bricks.BlankIcon({rate:2, flexShrink:0})); } if (this.role == 'llm'){ this.build_estimate_widgets(); } } build_estimate_widgets(){ if (!this.estimate_url) return; this.estimate_w = new bricks.HBox({width:'100%', cheight:2}); var txtw = new bricks.Text({ otext:'结果满意吗?', i18n:true, }); var likew = new bricks.Svg({rate:2, url:bricks_resource('imgs/like.svg')}); var unlikew = new bricks.Svg({rate:2, url:bricks_resource('imgs/unlike.svg')}); likew.bind('click', this.estimate_llm.bind(this, 1)); unlikew.bind('click', this.estimate_llm.bind(this, -1)) this.estimate_w.add_widget(txtw); this.estimate_w.add_widget(likew); this.estimate_w.add_widget(new bricks.BlankIcon({rate:1, flexShrink:0})); this.estimate_w.add_widget(unlikew); likew.set_style('cursor', 'pointer'); unlikew.set_style('cursor', 'pointer'); this.estimate_w.hide(); } async estimate_llm(val, event){ var desc = { "widgettype":"urlwidget", "options":{ "params":{ "logid":this.logid, "value":val }, "url":this.estimate_url } }; var icon = event.target.bricks_widget; icon.rate = 2; icon.charsize_sizing(); var w = await bricks.widgetBuild(desc, this); this.estimate_w.disabled(true); } async update_data(data){ if (this.run) { this.run.stop_timepass(); this.content.remove_widget(this.run); if(this.textvoice){ this.upstreaming = new bricks.UpStreaming({ url:this.tts_url }); this.upstreaming.go(); } } if (this.upstreaming){ this.upstreaming.send(data.content); } this.run = null; this.filler.clear_widgets(); if (typeof this.output_view === 'string'){ this.output_view = JSON.parse(this.output_view); } var desc = bricks.apply_data(this.output_view, data); var w = await bricks.widgetBuild(desc, this.llmio); if (! w){ console.log('widgetBuild() return null, desc=', this.output_view, desc, 'data=', data); return; } w.set_css('llm_msg'); w.set_style('width', '100%'); this.filler.add_widget(w); this.filler.add_widget(this.estimate_w); if (data.logid){ this.logid = data.logid; if (this.estimate_url){ this.estimate_w.show(); } } } finish(){ if (this.upstreaming){ this.upstreaming.finish(); } } } bricks.LlmModel = class extends bricks.JsWidget { /* { icon: model: url: output_view: params: user_message_format: system_message_format: llm_message_format: use_session: input_from: textvoice: tts_url: response_mode:stream, sync or async } */ constructor(llmio, opts){ super(opts); this.llmio = llmio; this.messages = []; } render_title(){ var w = new bricks.HBox({padding:'15px'}); w.bind('click', this.show_setup_panel.bind(this)) var img = new bricks.Svg({ rate:2, tip:this.opts.model, url:this.opts.icon||bricks_resource('imgs/llm.svg') }); // var txt = new bricks.Text({text:this.opts.model}); w.add_widget(img); // w.add_widget(txt); return w; } show_setup_panel(event){ } inputdata2uploaddata(data){ var d; if (data instanceof FormData){ d = bricks.formdata_copy(data); } else { d = objcopy(data); } var fmt = this.opts.user_message_format; if (fmt){ var umsg = bricks.apply_data(fmt, inputdata2dic(data)); this.messages.push(umsg); } if (data instanceof FormData){ d.append('model', this.opts.model) d.append('modelinstanceid', this.opts.modelinstanceid) d.append('modeltypeid', this.opts.modeltypeid) d.append('messages', JSON.stringify(this.messages)); } else { d.messages = JSON.stringify(this.messages); d.model = this.opts.model; d.modelinstanceid = this.opts.modelinstanceid; d.modeltypeid = this.opts.modeltypeid; } return d; } async model_inputed(data){ if (!opts.use_session){ this.messages = []; } var mout = new bricks.ModelOutput({ textvoice:this.textvoice, tts_url:this.tts_url, icon:this.opts.icon, model:this.opts.model, estimate_url:this.llmio.estimate_url, output_view:this.opts.output_view}); this.llmio.o_w.add_widget(mout); if (this.response_mode == 'stream' || this.response_mode == 'async') { var d = this.inputdata2uploaddata(data); console.log('data_inouted=', data, 'upload_data=', d); var hr = new bricks.HttpResponseStream(); var resp = await hr.post(this.opts.url, {params:d}); await hr.handle_chunk(resp, this.chunk_response.bind(this, mout)); this.chunk_ended(); } else { var d = this.inputdata2uploaddata(data); var hj = new bricks.HttpJson() var resp = await hj.post(this.opts.url, {params:d}); if (this.response_mode == 'sync'){ resp.content = bricks.escapeSpecialChars(resp.content) mout.update_data(resp); if (this.messages){ var msg = this.llm_msg_format(); var lmsg = bricks.apply_data(msg, resp); this.messages.push(lmsg) } } else { ; } } } is_accept_source(source){ if (this.opts.input_from == source){ return true; } return false; } llm_msg_format(){ return this.llm_message_format || {role:'assistant', content:"${content}"} } chunk_response(mout, l){ var d = JSON.parse(l); if (! d.content || d.content == ''){ return; } d.content = bricks.escapeSpecialChars(d.content); this.resp_data = d; mout.update_data(d); // console.log('stream data=', d); } chunk_ended(){ if (! this.messages) { console.log('this.messages is null !!!!!!!!!'); return; } var msg = this.llm_msg_format(); var txt = bricks.escapeSpecialChars(this.resp_data.content) this.resp_data.content = txt; var lmsg = bricks.apply_data(msg, this.resp_data); this.messages.push(lmsg); } } bricks.LlmIO = class extends bricks.VBox { /* options: { ws_url: user_icon: list_models_url: input_fields: input_view: output_view: models: } models:[ { icon: model: url: params: use_session: system_prompt: user_parmpt: input_from: io_mode:stream, sync or async } ] */ constructor(opts){ super(opts); this.ws = new bricks.WebSocket(this.ws_url); this.llmmodels = []; this.title_w = new bricks.HBox({cheight:3}); var bottom_box = new bricks.HBox({cheight:3}); this.i_w = new bricks.Svg({ rate:2, url:bricks_resource('imgs/input.svg'), margin:'14px', tip:'input data', css:'clickable' }); this.nm_w = new bricks.Svg({ rate:2, url:bricks_resource('imgs/add.svg'), margin:'14px', tip:'add new model', css:'clickable' }); bottom_box.add_widget(this.i_w); bottom_box.add_widget(this.nm_w); this.nm_w.bind('click', this.open_search_models.bind(this)); this.i_w.bind('click', this.open_input_widget.bind(this)); this.o_w = new bricks.Filler({overflow:'auto'}); this.add_widget(this.title_w); this.add_widget(this.o_w); if (this.models.length < 2 && this.tts_url){ this.textvoice = true; } this.add_widget(bottom_box); this.models.forEach( m =>{ this.show_added_model(m); }); } show_added_model(m){ if (this.textvoice){ m.textvoice = true; m.tts_url = this.tts_url; } var lm = new bricks.LlmModel(this, m); this.llmmodels.push(lm); var tw = lm.render_title(); this.title_w.add_widget(tw); } async open_search_models(event){ event.preventDefault(); event.stopPropagation(); var rect = this.showRectage(); var opts = { title:"select model", icon:bricks_resource('imgs/search.svg'), auto_destroy:true, auto_open:true, auto_dismiss:false, movable:true, top:rect.top + 'px', left:rect.left + 'px', width: rect.right - rect.left + 'px', height: rect.bottom - rect.top + 'px' } var w = new bricks.PopupWindow(opts); var sopts = { data_url:this.list_models_url, data_params:{ mii:this.models[0].modelinstanceid, mti:this.models[0].modeltypeid }, data_method:'POST', col_cwidth: 24, record_view:{ widgettype:"VBox", options:{ cheight:20, css:"card" }, subwidgets:[ { widgettype:"Title4", options:{ text:"${name}" } }, { widgettype:"Filler", options:{ css:"scroll" }, subwidgets:[ { widgettype:"VBox", options:{ css:"subcard" }, subwidgets:[ { widgettype:"Text", options:{ text:"模型描述:${description}", wrap:true } }, { widgettype:"Text", options:{ text:"启用日期:${enable_date}" } } ] } ] } ] } }; var cols = new bricks.Cols(sopts); cols.bind('record_click', this.add_new_model.bind(this)); cols.bind('record_click', w.dismiss.bind(w)); w.add_widget(cols); w.open(); } async add_new_model(event){ event.preventDefault(); event.stopPropagation(); this.models.push(event.params); this.show_added_model(event.params); } async open_input_widget(event){ event.preventDefault(); event.stopPropagation(); var rect = this.showRectage(); var opts = { title:"input data", icon:bricks_resource('imgs/input.svg'), auto_destroy:true, auto_open:true, auto_dismiss:false, movable:true, top:rect.top + 'px', left:rect.left + 'px', width: rect.right - rect.left + 'px', height: rect.bottom - rect.top + 'px' } var w = new bricks.PopupWindow(opts); var fopts = { fields:this.input_fields } var fw = new bricks.Form(fopts); fw.bind('submit', this.handle_input.bind(this)); fw.bind('submit', w.destroy.bind(w)); w.add_widget(fw); w.open(); } async handle_input(event){ var params = event.params; await this.show_input(params); for(var i=0;i