bricks/bricks/llm.js
2024-12-13 18:07:16 +08:00

474 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

bricks = window.bricks || {}
bricks.LlmMsgAudio = class extends bricks.UpStreaming {
constructor(opts){
super(opts);
this.olddata = '';
this.data = '';
this.cn_p = ["。","","","","\n"];
this.other_p = [".",",","!","?","\n"];
this.audio = AudioPlayer({})
}
detectLanguage(text) {
try {
const detector = new Intl.LocaleDetector();
const locale = detector.detectLocaleOf(text);
return locale.language;
} catch (error) {
console.error('无法检测语言:', error);
return '未知';
}
}
send(data){
var newdata = data.slice(this.olddata.length);
this.olddata = data;
this.data += newdata;
var lang = detectLaguage(this.data);
var parts;
if (lang='zh'){
parts = this.data.split(this.cn_p).filter(part => part.trim()!== '');
} else {
parts = this.data.split(this.oter_p).filter(part => part.trim()!== '');
}
for(var i=0;i<parts.length - 1; i++){
super.send(parts[i]);
}
this.data = parts[parts.length - 1];
}
async go(){
var resp = await super.go();
this.audio.set_source_from_response(resp);
return resp;
}
}
bricks.ModelOutput = class extends bricks.HBox {
/* {
icon:
output_view:
}
完成模型输出的控件的初始化以及获得数据后的更新, 更新是的数据在流模式下,需要使用累积数据
*/
constructor(opts){
if(! opts){
opts = {};
}
opts.width = '100%';
super(opts);
this.logid = null;
this.img = new bricks.Icon({
rate:2,
tip:this.opts.model,
url:this.icon||bricks_resource('imgs/llm.png')
});
this.run = new bricks.BaseRunning({target:this});
this.add_widget(this.img);
this.add_widget(this.run);
this.filler = new bricks.VBox({});
this.filler.set_css('filler');
this.add_widget(this.filler);
this.add_widget(new bricks.BlankIcon({rate:2, flexShrink:0}));
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.Icon({rate:2, url:bricks_resource('imgs/like.png')});
var unlikew = new bricks.Icon({rate:2, url:bricks_resource('imgs/unlike.png')});
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.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;
var w = await bricks.widgetBuild(this.output_view, this.llmio, data);
w.set_css('llm_msg');
w.set_style('width', '100%');
this.filler.clear_widgets();
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:
mapi:
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({});
w.bind('click', this.show_setup_panel.bind(this))
var img = new bricks.Icon({
rate:2,
url:this.opts.icon||bricks_resource('imgs/llm.png')
});
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 = 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);
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:
{
user_icon:
list_models_url:
input_fields:
input_view:
output_view:
models:
}
models:[
{
icon:
model:
mapi:
url:
params:
use_session:
system_prompt:
user_parmpt:
input_from:
io_mode:stream, sync or async
}
]
*/
constructor(opts){
super(opts);
this.llmmodels = [];
this.title_w = new bricks.HBox({cheight:3});
var bottom_box = new bricks.HBox({cheight:3});
this.i_w = new bricks.Icon({
rate:2,
url:bricks_resource('imgs/input.png'),
margin:'14px',
tip:'input data',
css:'clickable'
});
this.nm_w = new bricks.Icon({
rate:2,
url:bricks_resource('imgs/add.png'),
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.png'),
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.png'),
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<this.llmmodels.length;i++){
var lm = this.llmmodels[i];
if (lm.is_accept_source('userinput')){
schedule_once(lm.model_inputed.bind(lm, params), 0.01);
// await lm.model_inputed(params);
}
};
}
async show_input(params){
var box = new bricks.HBox({width:'100%'});
var data = inputdata2dic(params);
console.log('data=', data, 'input_view=', this.input_view);
var w = await bricks.widgetBuild(this.input_view, this.o_w, data);
w.set_css(this.msg_css||'user_msg');
w.set_css('filler');
var img = new bricks.Icon({rate:2,url:this.user_icon||bricks_resource('imgs/user.png')});
box.add_widget(new bricks.BlankIcon({rate:2, flexShrink:0}));
box.add_widget(w);
box.add_widget(img);
this.o_w.add_widget(box);
}
}
bricks.Factory.register('LlmIO', bricks.LlmIO);