//message 聊天消息 import win.version; import string.html; namespace web.rest.aiChat; class message{ ctor( messages ){ this = ..table.array(); this.limit = 15; if(#messages) { ..table.append(this,messages); this.limit = 0; } }; push = function(msg){ if(type.isString(msg)){ var lastMsg = this[#this] if(lastMsg[["role"]]!="assistant"){ lastMsg = { role = "assistant"; content = msg||""; }; ..table.push(this,lastMsg); return; } elseif(msg) { lastMsg.content = lastMsg.content++msg; } } elseif(msg) { //保留消息队列前两个与最后两个可能是系统提示词的消息 if(this.limit && (#this-4)>=this.limit ){ ..table.splice(this,3,(#this-4)-this.limit+1) } //加入聊天消息 ..table.push(this,msg); } else{ var lastMsg = this[#this] if(lastMsg[["role"]]=="assistant"){ lastMsg[["content"]] = ..string.replace(lastMsg[["content"]] ,"\<(|)\>(.+?)\",""); } } } aardioSystem = function(prompt){ var p = ""; if(!this.aardioSystemPrompt){ p = p ++ aardioSystemPrompt; var qr = ..string.load("~\doc\guide\language\syntax-quick-ref.md"); if(qr){ p = p + '\r\n\r\n---\r\n\r\n' + qr; } this.aardioSystemPrompt = true; } if(#prompt){ p = p + '\r\n\r\n---\r\n\r\n' + prompt; } if(#p) this.system(p); } findSystem = function(pattern){ for(i=1;#this;1){ var msg = this[i] if(msg[["role"]]=="system"){ if(msg.content && ..string.find(msg.content,pattern) ){ return msg.content; } } } }; system = function(content){ if(#content){ this.push( { role = "system"; content = content; } ); } }; prompt = function(content,imageUrl){ if(imageUrl){ var typeImage = type(imageUrl); if((typeImage=="string") && ..io.exist(imageUrl)){ imageUrl = ..string.loadBuffer(imageUrl); if(imageUrl) typeImage ="buffer"; } if(typeImage==="buffer"){ var b = imageUrl; if( b[1] == 0xFF && b[2] == 0xD8 ){ b="image/jpeg" } elseif( b[1] == 0x89 && b[2] == 0x50 && b[3] == 0x4e && b[4] == 0x47){ b="image/png" } elseif( b[1] == 0x47 && b[2] == 0x49 && b[3] == 0x46){ b="image/gif" } elseif( b[1] == 0x52 && b[2] == 0x49 && b[3] == 0x46 && b[4] == 0x46){ b="image/webp" } else{ error("错误的图像数据",2); } import crypt; imageUrl = ..string.format('data:%s;base64,%s',b,..crypt.encodeBin(imageUrl,,0x40000001/*_CRYPT_STRING_BASE64*/ ) ) } content = { { "type": "text", "text": content }, { "type": "image_url", "image_url": { "url": imageUrl } } } } this.push( { role = "user"; content = content; } ); }; assistant = function(content){ this.push(content); }; url = function(url){ var sysMsg = ""; if(type(url)!="table" || url[["url"]]) url = {url}; for(i=1;#url;1){ var md,err,u = httpGet(url[i]); if(!md) continue; sysMsg = sysMsg + '\r\n\r\n用户输入的参考网址:' + u + '\r\n\r\n下面是自该网址获取的' +(!err?" Markdown ":"文本")+'格式内容:' + '\r\n\r\n' + md +'\r\n\r\n------------------------\r\n\r\n' } if(!#sysMsg) return null,err; this.system(sysMsg); return sysMsg; }; clear = function(){ for(i=#this;1;-1){ ..table.remove(this,i); } this.aardioSystemPrompt = null; }; started = function(){ for(i=1;#this){ if(this[i][["role"]]!="system"){ return true; } } }; } /*****intellisense() web.rest.aiChat.message() = 创建 AI 聊天助手消息队列。\n可选用参数 @1 指定初始化消息数组。\n可以在界面线程单独创建与管理消息队列,\n然后再通过线程参数发给后台线程的 web.rest.aiChat 对象。\n!webRestChatMessage. end intellisense*****/ /*****intellisense(!webRestChatMessage) started() = 是否已经发起对话。\n发起对话指的是消息队列中包含非 system 角色的消息。 clear() = 清除会话记录。 assistant(__/*增量文本*/) = 显示并记录 AI 接口返回的增量文本。\n参数必须是文本或者表示输出完成的 null 值。 prompt( = 添加提示词。 prompt(.(content) = 添加用户提示词。\ncontent 可以是字符串,也可以是对象数组。\n请参考调用模型与接口的文档了解支持的对象格式。 prompt(.(txtContent,imageUrl) = 添加并显示用户 @txtContent 参数指定的文本提示词。\n可选用 @imageUrl 指定图像网址(通常不支持远程网址),\n本地图像可调用 inet.urlData 转换为 Data URL(Base64) 格式。\n注意请参考接口文档了解调用模型是否支持图像识别。 prompt(.(txtContent,imageBufferOrPath) = 添加并显示用户 @txtContent 参数指定的文本提示词。\n可选用 @imageBufferOrPath 指定图像数据(必须是 buffer 对象),\n支持 JPG,PNG,GIF,WEBP 格式图像。\n\n注意请参考接口文档以了解调用模型是否支持图像识别。\n\n如果 @imageBufferOrPath 使用字符串指定文件路径则调用 string.loadBuffer 读取为 buffer。 system(__/*提示词*/) = 添加并显示系统提示词。 url(__) = 获取参数 @1 指定网址的内容并转换为 Markdown 格式或者文本格式提示词。\n参数 @1 也可以指定包含 url 字段的对象,或这样的对象数组。\n参数为 包含 url 字段的对象时如果同时包含 text 字段,则直接自 text 字段获取网页文本。\n如果参数 @1 指定数组参数,则将所有数组成员作为参数获取目标内容。\n如果参数为空数组、空字符串、null 则返回 null。\n成功返回添加的提示词,失败返回 null,错误信息 。\n\n如果提前导入 web.turndown 库则支持自动转换 HTML 为 Markdown 格式文本。 aardioSystem( = 自动创建 aardio 编程助手系统提示词。\n除函数自带的基本的 aardio 助手提示词以外,EXE 目录下必须存在待添加的文档。 aardioSystem(.(prompt) = 增加 aardio 编程助手提示词(如果已添加则忽略)。\n如果指定参数 prompt 则追加到系统提示词(不检测之前是否已添加过)。 aardioSystemPrompt = 是否自动附加了 aardio 编程助手系统提示词。\n不可手动修改此属性。 limit = 可选指定一个限制消息队列大小的数值。\nlimit 限制的队列大小不包含第一个系统提示词与最后一个系统提示词。 findSystem(__/*模式串*/) = 使用模式匹配语法查找消息队列中是否存在指定的系统提示词 end intellisense*****/ namespace message{ aardioSystemPrompt = ..string.removeBom($"~\doc\guide\ide\system-prompt.md"); httpGet = function(url){ if(type(url)==="table" && #url){ url = url[1]; url = url[["url"]] || url; if(url && ..string.find(url,"(https?\://?aardio\.com/zh\-cn/doc/?$") ){ url = url[2] || url; } } var snippet; if(url[["url"]]){ var rawText = url[["text"]] || url[["raw_content"]]; if(rawText){ return rawText,"text",url[["url"]]; } snippet = url[["snippet"]] || url[["content"]]; } url = url[["url"]] || url; if(!#url) return; var docUrl = ..string.match(url,"(https?\://?aardio\.com/zh\-cn/doc/\S+)\.|") if( docUrl ){ url = docUrl + ".html.md"; var r,e = ..inet.http.get(url); if(r) return r,,url; return null,e; } else{ var html,err = ..inet.http.get(url); if(html){ if(!_WIN10_LATER || !..web[["turndown"]]) return ..string.html.toText(html),"text",url; var md; try { var turndownService = ..web.turndown.default(); md = turndownService.turndown( html ); } catch(e){ err = e; } if(#md) return md,,url } if(snippet){ return snippet,"text",url; } return null,err; } } }