import win.ui; import fonts.fontAwesome; /*DSG{{*/ var winform = win.form(text="aardio - AI 编程助手";right=759;bottom=620;bgcolor=0xFAF9F8;border="none") winform.add( bk={cls="bk";text="aardio - AI 编程助手";left=21;top=3;right=198;bottom=23;align="left";dl=1;dt=1;z=15}; bkPrompt={cls="plus";left=5;top=461;right=754;bottom=615;bgcolor=0xFFFFFF;border={radius=12};clip=1;clipBk=false;db=1;disabled=1;dl=1;dr=1;z=1}; bkTitle={cls="bk";left=0;top=0;right=764;bottom=27;bgcolor=0xDBDAD9;dl=1;dr=1;dt=1;forecolor=0x5C5B5B;linearGradient=0;z=2}; btnClear={cls="plus";text="清除";left=601;top=586;right=659;bottom=615;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF014';notify=1;textPadding={left=25};z=5}; btnCopy={cls="plus";text="复制";left=315;top=586;right=379;bottom=615;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;disabled=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0C5';notify=1;textPadding={left=25};z=10}; btnSearch={cls="plus";text="联网";left=17;top=586;right=78;bottom=615;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF26B';notify=1;textPadding={left=25};z=16}; btnSend={cls="plus";text="问 AI";left=668;top=586;right=740;bottom=615;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dr=1;font=LOGFONT(h=-13);iconColor=0x5757FF;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0AA';notify=1;textPadding={left=25};z=4}; btnSetting={cls="plus";text="设置";left=81;top=586;right=148;bottom=615;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF013';notify=1;textPadding={left=25};z=6}; btnSnap={cls="plus";left=277;top=587;right=310;bottom=616;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;disabled=1;dr=1;font=LOGFONT(h=-13);iconStyle={font=LOGFONT(h=-13;name='FontAwesome')};iconText='\uF030';notify=1;textPadding={left=25};z=12}; chkFix={cls="plus";text="更正";left=388;top=586;right=448;bottom=615;align="left";bgcolor=0xFFFFFF;db=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=11}; cmbAgent={cls="combobox";left=151;top=590;right=275;bottom=612;db=1;dl=1;edge=1;items={};mode="dropdownlist";z=17}; editMaxTokens={cls="edit";left=530;top=590;right=573;bottom=613;align="right";db=1;dr=1;edge=1;z=8}; editPrompt={cls="richedit";left=8;top=466;right=751;bottom=580;autohscroll=false;bgcolor=0xFFFFFF;db=1;dl=1;dr=1;link=1;multiline=1;vscroll=1;z=3}; spinMaxTokens={cls="spin";left=574;top=591;right=594;bottom=613;db=1;dr=1;z=7}; splitter={cls="splitter";left=-7;top=457;right=758;bottom=462;bgcolor=0xD1D1D1;db=1;dl=1;dr=1;horz=1;z=13}; static={cls="static";text="回复长度:";left=455;top=586;right=521;bottom=615;align="right";bgcolor=0xFFFFFF;center=1;db=1;dr=1;z=9}; wndBrowser={cls="custom";text="自定义控件";left=5;top=27;right=753;bottom=455;ah=1;db=1;dl=1;dr=1;dt=1;z=14} ) /*}}*/ import fsys.table; config = fsys.table(io.appData("aardio/ide/aiChat/~")) if(!config.itemData) { config.itemNames={[1]="默认"}; config.itemData={[1]={proxy="";temperature=0.4;key='\0\1\96';model="aardio";url="https://ai.aardio.com/api/v1/";systemPrompt="";msgLimit=15}}; ..table.assign(config,config.itemData[1]); config.selItem=1; } var aiChatConfig = {} if(config.selItem){ aiChatConfig = ..table.assign({},config.itemData[config.selItem] ); } //创建显示聊天消虑的 Web 浏览器窗口 import web.form.chat; var wb = web.form.chat(winform.wndBrowser); wb.enableKatex(aiChatConfig.katex); //清除上下文 var resetMessages = function(){ wb.clear(); //自动生成 aardio 编程助手系统提示词 wb.aardioSystem(aiChatConfig.systemPrompt); wb.aiSystemPropmptSupperHotkeys = null; wb.aiSystemPropmptStringPatterns = null; wb.aiSystemPropmptWebView = null; wb.aiSystemPropmptPython = null; wb.aiSystemPropmptWinform = null; wb.aiSystemPropmptNet = null; wb.aiSystemPropmptPlus = null; wb.aiSystemPropmptFile = null; wb.aiSearched = null; import ide; var ideEnv = '## 当前运行环境\r\n- 当前 aardio 主版本号为:' + (ide.getConfig("version") || (_AARDIO_VERSION)); ideEnv = ideEnv ++ '\r\n- 当前时间为:' + tostring(..time()); ideEnv = ideEnv ++ '\r\n- 当前进程 EXE 执行文件名为:"' + ..io._exefile + '"'; ideEnv = ideEnv ++ '\r\n- 当前操作系统为:"' + ..win.version.format() + '"'; var projPath = ide.getProjectPath(); if(!#projPath) return wb.system(ideEnv); import fsys.file; var file = fsys.file(projPath,"r"); if(!file) return; if(file.size() > 20000){ file.close(); return; } var xml = file.readAll(); if(string.indexOf(xml,"\node_modules\")){ winform.msgboxErr(" 请不要将 node_modules 目录添加到 aardio 工程。 aardio 工程的 Web 前端源码目录请设置【忽略目录】为 true 。 前端发布文件目录请设置【本地构建】为 true 。 否则将严重影响性能与 AI 相关功能。"); } else { var project = '\r\n\r\n'+" ## aardio 源文件与工程文件 aardio 代码文件的后缀名为 `.aardio`,可包含 UTF-8 编码的源代码,也可以包含编译后的二进制代码。 aardio 工程文件的后缀名为 `.aproj`,其内容是 XML 格式的工程配置,也使用 UTF-8 编码 。 " project = project + '\r\n\r\n**用户当前在 aardio 开发环境中打开的工程文件路径是: **"'+projPath+'"\r\n' project = project + '\r\n\r\n**用户当前打开的工程文件内容如下:**' project = project + '\r\n\r\n```xml\r\n' + ..string.removeBom(xml) + '\r\n```\r\n\r\n' var desc = /***** **工程文件各 XML 节点的作用与含义:** - project 元素指定工程配置,并作为工程根目录包含其他 folder 或 file 元素。 project 元素的属性 ui 指定图形界面。 * 如果 ui 为 "win" 则为图形界面发布后运行默认不显示控制台。 * 如果 ui 为"console" 则为控制台程序发布后运行时默认显示控制台窗口。 project 元素的属性 dstrip 指定是否移除调试符号。 * `dstrip="true"` 则发布后移除调试信息,生成的文件更小但错误信息会缺少调试信息(例如文件名行号)。 - folder 元素为工程中的虚拟目录 如果 folder 的属性 embed 为 "true" 则该目录发布后嵌入 EXE 资源文件,aardio 中很多函数和库都自动支持这种嵌入资源而不需要额外修改代码。例如对于 `string.load("/res/test.txt")`,无论参数指定的文件是不是 EXE 资源文件函数的返回值都是一样的,这是 aardio 的一个主要特性。 如果 folder 元素的属性 local 为 "true" 则表示这是一个本地目录(通常也是 Web 前端工程的发布目录),发布为 EXE 时将添加该目录下的所有文件。这种目录在工程中不显示子级文件或目录,右键菜单的『同步本地目录』也是无效的。 如果 folder 元素的属性 ignored 为 "true" 是指这个目录在发布时被忽略(ignored)。这种目录通常用来指向包含 Web 前端工程源码的目录,工程本身其实并不需要这些多余的目录,生成 EXE 时也会忽略这种目录。 - file 元素则表示添加到工程中的文件 在工程根目录下只能有一个应用程序启动文件, 文件路径必须是 `main.aardio` 或以 `.main.aardio` 结束。除了启动文件,工程根目录只能包含 folder 元素。 *****/ project = ideEnv ++ project + desc + '\r\n\r\n'; var codePath = ide.getActiveDocPath(); if(#codePath && ..string.endsWith(codePath,".aardio",true) ){ project = project + '**用户当前正在编辑的文件为:** "'+codePath+'"\r\n' } wb.system(project); } } resetMessages(); if(_ASK_AI_SYSTEMP_PROMPT){ wb.system(_ASK_AI_SYSTEMP_PROMPT); } if(_ASK_AI_USER_PROMPT){ winform.editPrompt.text = _ASK_AI_USER_PROMPT; } winform.btnClear.oncommand = function(id,event){ resetMessages();//清除聊天上下文 winform.editPrompt.setFocus(); } winform.splitter.origTop = winform.splitter.top; import thread.event; var eventStop = thread.event(); //响应按键事件,输入用户提示词 winform.btnSend.oncommand = function(id,event){ if(winform.btnSend.text == "停止"){ eventStop.set(); winform.btnSend.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'} return; } var prompt = winform.editPrompt.text; if(!#prompt){ wb.errorMessage("请先输入问题。") winform.editPrompt.setFocus(); return; } var prompt,isErrMsgBox = ..string.replace(prompt,"\s*✅\s*返回代码编辑器按 F1 键 AI 纠错\s*-+\s*确定\s*-+\s*",""); var tApiUrl = inet.url.split(aiChatConfig.url); if(!tApiUrl){ wb.errorMessage(`错误的接口网址,;点这里重新设置`) winform.editPrompt.setFocus(); return; } winform.btnSend.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'} winform.btnClear.disabled = true; winform.btnSnap.disabled = true; winform.chkFix.disabled = true; wb.chatMessage.limit = aiChatConfig.msgLimit; var assistantMsg = wb.lastAssistantMessage(); if(assistantMsg && winform.chkFix.checked){ //Few-shot Learning assistantMsg.content = ide.aifix.markdown(assistantMsg.content,true); } //输入 AI 提示词 wb.prompt( prompt ); winform.editPrompt.text = ""; if(isErrMsgBox){ wb.body.insertAdjacentHTML("beforeEnd",`
复制的错误信息缺少代码上下文,请在关闭错误对话框 30 秒内回到代码编辑器按 F1 键。
`); } if(string.indexOf(prompt,`"""选中代码"""`)){ } elseif(wb.aiSearched || (aiChatConfig.model && ( ..string.endsWith(aiChatConfig.model,":online") || ..string.startsWith(aiChatConfig.model,"aardio") ) ) ){ } elseif( (config.search[["mode"]]=="exa" && config.search.exa ) ||(config.search[["mode"]]=="tavily" && config.search.tavily ) ){ wb.showLoading("正在联网搜索"); import web.turndown; var ok = thread.invokeAndWait( function(wb,prompt,searchConfig){ import web.rest.jsonClient;; if(searchConfig.mode=="exa"){ //导入 Exa 索接口 var exaClient = web.rest.jsonClient(); exaClient.setHeaders({ "x-api-key": searchConfig.exa.key} ) var exa = exaClient.api("https://api.exa.ai/"); //搜索 var searchData,err = exa.search({ query:"aardio 编程语言文档 范例 " + prompt, contents={text= true}, numResults: searchConfig.exa.count || 2, includeDomains: searchConfig.exa.includeDomains, excludeDomains: !searchConfig.exa.includeDomains ? searchConfig.exa.excludeDomains : null, type:"keyword" //一般 keyword 搜索就够了(价格低一些) }) var ret = searchData[["results"]] if(ret){ wb.url(ret); return true; } elseif(err){ wb.errorMessage(err); } } elseif(searchConfig.mode=="tavily"){ var http = web.rest.jsonClient(); http.setAuthToken(searchConfig.tavily.key); var tavily = http.api("https://api.tavily.com"); //搜索 var searchData,err = tavily.search({ query = "aardio 编程语言文档 范例 " + prompt, includeDomains = searchConfig.exa.includeDomains, excludeDomains: !searchConfig.exa.includeDomains ? searchConfig.exa.excludeDomains : null, max_results = searchConfig.exa.count || 2 //返回的搜索结果数量,不必要太多,前两三条就可以了 }) //将搜索结果添加到系统提示词 var ret = searchData[["results"]] if(ret){ wb.url(ret); return true; } elseif(err){ wb.errorMessage(err); } } },wb,prompt,config.search) if(!ok){ winform.btnSend.disabledText = null; winform.btnClear.disabled = false; winform.btnSnap.disabled = false; winform.chkFix.disabled = false; return; } wb.aiSearched = true; wb.showLoading("正在思考") } var knowledge = "" if(!string.find(prompt,"<```>||||")){ prompt = string.replace(prompt,"![""'`\w]https?\://[^\s\)""']+", function(url){ if(!..string.match(url,"<@@.js@>|<@@.css@>|<@@.jpg@>|<@@.png@>|<@@.gif@>|<@@.webp@>$") ){ wb.showLoading("正在读取:"+url) import web.turndown; var md,err = web.turndown.httpGet(url) if(!#md) return; md = '\r\n\r\n用户输入的参考网址:' + url + '\r\n\r\n下面是自该网址获取的' +(err?" Markdown ":"文本")+'格式内容:' + '\r\n\r\n' + md +'\r\n\r\n------------------------\r\n\r\n' knowledge = knowledge ++ md; } }); } if(#knowledge){ wb.system(knowledge) if(string.match(prompt,`^\s*(https?\://[^\s()"']+)\s*$`)){ prompt = "解读分析与总结要点 " + prompt; } } aiChatConfig.maxTokens = winform.spinMaxTokens.pos; winform.splitter.splitAt(winform.splitter.origTop); eventStop.reset(); var loading = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250' } winform.btnSend.disabledText = null; winform.btnSend.text = "停止" winform.btnSend.reduce(loading,function(value,index){ if(value) winform.btnSend.iconText = value; return 150; } ) //创建多线程向服务端发送请求 thread.invoke( function(wb,aiChatConfig,eventStop){ for(k,v in aiChatConfig){ if(v==="")aiChatConfig[k] = null; } if(!#aiChatConfig.key){ aiChatConfig = table.assign(aiChatConfig,{ url = "https://ai.aardio.com/api/v1/"; model = "aardio"; temperature = 0.1; }); } //导入调用 HTTP 接口的 REST 客户端 import web.rest.aiChat; aiChatConfig.userAgent = "Mozilla/5.0 (Windows NT "+ _WIN_VER_MAJOR +"."+_WIN_VER_MINOR+"; aardio; rv:"+_AARDIO_VERSION+") like Gecko"; var client = web.rest.aiChat(aiChatConfig); client.referer = "https://aardio.com"; client.setHeaders({ "X-Title":"aardio"}); var ok,err = client.messages(wb.chatMessage,function(deltaText,deltaReasoning){ if(eventStop.wait(0)){ return false; } if(#deltaReasoning){ wb.showThinking(deltaReasoning); return; } wb.assistant(deltaText); } ); if(err){ //获取错误对象(解析 JSON 格式的错误信息) var errObject = client.lastResponseError() if(errObject[["error"]][["type"]] == "authentication_error" ){ wb.errorMessage(`API 密钥错误!点这里获取密钥, 点这里设置新密钥`) } else { wb.errorMessage(err) } } elseif(!ok){ wb.errorMessage("错误代码:" + ( client.lastStatusCode : "未知 " + (client.lastResponseString() || ""))) } if(ok){ //在 AI 对话尾部增加复制  不喜欢  喜欢  的反馈按钮 wb.insertAdjacentHTML("beforeEnd", `
复制代码    满意    转人工
`) } },wb,aiChatConfig,eventStop//将参数传入线程 ) winform.btnCopy.disabled = false; } //在 AI 回复结束后回调此函数 wb.onWriteEnd = function(){ winform.btnSend.disabledText = null; winform.btnSend.reduce(false); winform.btnSend.text = "问 AI"; winform.btnSend.iconText = '\uF0AA'; winform.btnClear.disabled = false; winform.btnCopy.disabled = false; winform.btnSnap.disabled = false; winform.chkFix.disabled = false; winform.editPrompt.setFocus(); } //在 AI 回复结束以前回调此函数,自动修正 aardio 代码块中的常见幻觉错误 import ide.aifix; wb.beforerWriteEnd = function(markdown){ if(winform.chkFix.checked) { return ide.aifix.markdown(markdown,true); } return markdown; } //导出 aardio 函数到网页 JavaScript 中。 wb.external = { updateApiKey = function(){ winform.btnSetting.oncommand(); } copyCode = function(){ var md = wb.lastMarkdown(); if(!#md) return winform.msgboxErr("消息为空。"); var found; for indent,_,code in string.gmatch(md,"!\N([ \t]*)(```+)(.+?)!\N\s*\2![^`\S]") { if(#indent){ text = string.replace(text,"\n+"+indent,'\n'); } if(winform.chkFix.checked) code = ide.aifix(code,true); win.clip.write( code ); found = true; } if(!found){ return winform.msgboxErr("没有找到代码块。"); } else{ winform.msgbox("已复制最后一个代码段到剪贴板。 您可以点窗口底部的「复制」按钮复制全部 Markdown 格式文本, 然后通过 aardio 代码编辑器右键菜单的「粘贴与更正」提取并合并多个代码段。") } } like = function(){ if(inet.url.split(aiChatConfig.url).host!="ai.aardio.com"){ winform.msgboxErr("仅 VIP 专属接口可用!") return; } import web.rest.jsonLiteClient; var http = web.rest.jsonLiteClient(); http.setAuthToken(aiChatConfig.key); var resp = http.post( "https://ai.aardio.com/api/v1/report/",{ message = JSON.stringify(wb.chatMessage); like = true; }) winform.msgbox( resp[["message"]] || "提交失败"); } dislike = function(){ if(inet.url.split(aiChatConfig.url).host!="ai.aardio.com"){ winform.msgboxErr("仅 VIP 专属接口可用!") return; } import web.rest.jsonLiteClient; var http = web.rest.jsonLiteClient(); http.setAuthToken(aiChatConfig.key); var resp = http.post( "https://ai.aardio.com/api/v1/report/",{ message = JSON.stringify(wb.chatMessage); like = false; }) winform.msgbox( resp[["message"]] || "提交失败"); } } import key; import win.clip; winform.btnCopy.oncommand = function(id,event){ var md = wb.lastMarkdown(); if(!#md) return winform.msgboxErr("消息为空。"); if(key.getState("CTRL")){ var found; for indent,_,code in string.gmatch(md,"!\N([ \t]*)(```+)(.+?)!\N\s*\2![^`\S]") { if(#indent){ text = string.replace(text,"\n+"+indent,'\n'); } if(winform.chkFix.checked) code = ide.aifix(code,true); win.clip.write( code ); found = true; } if(!found){ return winform.msgboxErr("没有找到代码块。"); } } else{ if(winform.chkFix.checked) md = ide.aifix.markdown(md,true); win.clip.write( md ) } winform.btnCopy.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250';text=''} thread.delay(800); winform.btnCopy.disabledText = null; winform.editPrompt.setFocus(); } //设置接口地址与 API 令牌的窗口 winform.btnSetting.oncommand = function(id,event){ ..table.assign(config,{ title = "aardio - 设置 AI 聊天助手"; f1Title = "设为 F1 键编码助手"; hyperlink1Title = "VIP"; f1MultiSelect = false; allowKatex = wb.documentMode>=11; }); import web.rest.aiChat.settingForm; var frmSetting = web.rest.aiChat.settingForm(winform,config); frmSetting.onSelChange = function(selIndex,configItem){ var currentSelIndex = winform.cmbAgent.selIndex; winform.cmbAgent.items = config.itemNames; if(currentSelIndex<#config.itemNames){ winform.cmbAgent.selIndex = currentSelIndex; } else{ winform.cmbAgent.selIndex = #config.itemNames; } } frmSetting.onHyperlink = function(apiUrl,apiKey){ if(!#apiKey){ frmSetting.msgboxErr("请先输入接口密钥( API key )") frmSetting.editApiKey.setFocus(); return; } import web.form; var frmWeb = win.form(text="aardio - VIP 会员中心" ) var wb = web.form( frmWeb ); wb.external = { openUrl = function(url){ import process; process.openUrl(url); frmWeb.close(true); } } frmWeb.show(3/*_SW_MAXIMIZE*/); wb.post("https://ai.aardio.com/vip/",{apiKey=apiKey}); } if( frmSetting.doModal(winform) ){ var configItem = config.selItem ? config.itemData[config.selItem] if(configItem){ aiChatConfig = table.assign({},configItem); winform.cmbAgent.items = config.itemNames; winform.cmbAgent.selIndex = config.selItem; if(!wb.started()){ if( !( _ASK_AI_SYSTEMP_PROMPT && string.find(winform.editPrompt.text,`<"""选中代码""">|<"""报错信息""">`) )){ resetMessages(); } } } } winform.editPrompt.setFocus(); } winform.chkFix.checked = true; winform.chkFix.oncommand = function(id,event){ var md = wb.getMarkdown(); if(owner.checked){ md = ide.aifix.markdown(md,true) } wb.write(md); winform.editPrompt.setFocus(); } var tip = /* - 在代码编辑器按 `F1` 键可调用`当前编码助手`帮您续写或补全代码(如有选区则打开文档)。 * 建议在输入光标前用`行注释`说明需求,再按 `F1` 键。 * 运行报错后 30 秒内在代码编辑器按 F1 ,无选区则调用 AI 纠错。 * 使用第三方密钥因无法接入专业版 aardio 知识库,生成的回复与代码质量会严重下降。 * ✨ 点此购买专业版密钥(API Key) ⚒ 设置新密钥 💡 找回密钥 - 聊天界面可联网读取提示词内的网页链接,并自动转换为 Markdown 格式文本。 - 按住 `Ctrl` 键点下面的 `复制` 按钮可复制 AI 最后一次输出的代码块。 - 按住 `Ctrl` 键点下面的 按钮可截长屏到剪贴板。 - 按住 `Ctrl+Enter` 可直接发送问题 。。 */ wb.write(tip) //默认设置输入框焦点 winform.editPrompt.setFocus(); winform.splitter.ltMin = 200; winform.splitter.rbMin = 150; var scrollbarHeight = ::User32.GetSystemMetrics(3/*_SM_CYHSCROLL*/) winform.editPrompt.onOk = function(ctrl,alt,shift){ if(ctrl){ winform.btnSend.oncommand(); return true; } var pt = ::POINT() ::User32.GetCaretPos(pt) var lineCount = winform.editPrompt.lineCount; var lineHeight = math.ceil(pt.x / lineCount + winform.dpiScale(5)); if(pt.y+(lineHeight+scrollbarHeight)*3>winform.editPrompt.height){ winform.wndBrowser.setRedraw(false) winform.splitter.splitMove(-lineHeight) winform.wndBrowser.setRedraw(true) } } //拆分界面 winform.splitter.split(winform.wndBrowser,{winform.bkPrompt,winform.editPrompt}); winform.editPrompt.enablePopMenu(function(){ return { { '问 AI(发送)\tCtrl+Enter'; function(id){ winform.btnSend.oncommand(); }; 0}; { /*分隔线*/ }; { (wb.aiSystemPropmptStringPatterns?"已自动":"")+"插入 模式匹配语法文档"; function(id){ winform.editPrompt.selText = " [aardio 模式匹配语法](https://www.aardio.com/zh-cn/doc/library-guide/builtin/string/patterns.html.md) " }; wb.aiSystemPropmptStringPatterns?1/*_MF_GRAYED*/: 0}; { (wb.aiSystemPropmptWebView?"已自动":"")+"插入 web.view 指南(网页相关)"; function(id){ winform.editPrompt.selText = " [web.view 使用指南](https://www.aardio.com/zh-cn/doc/library-guide/std/web/view/_.html.md) " }; wb.aiSystemPropmptWebView?1/*_MF_GRAYED*/: 0}; { "插入 web.rest 指南(HTTP 相关)"; function(id){ winform.editPrompt.selText = " [web.rest 使用指南](https://www.aardio.com/zh-cn/doc/library-guide/std/web/rest/client.html.md) " }; 0}; { "插入 多线程入门"; function(id){ winform.editPrompt.selText = " [多线程开发入门](https://www.aardio.com/zh-cn/doc/guide/language/thread.html.md) " }; 0}; { "插入 高级选项卡指南(多窗口)"; function(id){ winform.editPrompt.selText = " [高级选项卡指南](https://www.aardio.com/zh-cn/doc/library-guide/std/win/ui/tabs/_.html.md) " }; 0}; { (wb.aiSystemPropmptPlus?"已自动":"")+"插入 plus 控件指南(界面美化)"; function(id){ winform.editPrompt.selText = " [plus 控件使用指南](https://www.aardio.com/zh-cn/doc/library-guide/std/win/ui/ctrl/plus.html.md) " }; wb.aiSystemPropmptPlus?1/*_MF_GRAYED*/: 0}; { "插入 自定义控件指南"; function(id){ winform.editPrompt.selText = " [自定义控件使用指南](https://www.aardio.com/zh-cn/doc/library-guide/std/win/ui/ctrl/custom.html.md) " }; 0}; { (wb.aiSystemPropmptPython?"已自动":"")+"插入 调用 Python 文档"; function(id){ winform.editPrompt.selText = " [aardio 调用 Python 入门指南](https://www.aardio.com/zh-cn/doc/library-guide/ext/python/_.html.md) " }; wb.aiSystemPropmptPython?1/*_MF_GRAYED*/: 0}; { (wb.aiSystemPropmptNet?"已自动":"")+"插入 调用 NET 文档"; function(id){ winform.editPrompt.selText = " [aardio 调用 .NET 入门指南](https://www.aardio.com/zh-cn/doc/library-guide/std/dotNet/_.html.md) " }; wb.aiSystemPropmptNet?1/*_MF_GRAYED*/: 0}; { (wb.aiSystemPropmptSupperHotkeys?"已自动":"")+"插入 超级热键文档"; function(id){ winform.editPrompt.selText = " [超级热键使用指南](https://www.aardio.com/zh-cn/doc/library-guide/std/key/hotkey.html.md) " }; wb.aiSystemPropmptSupperHotkeys?1/*_MF_GRAYED*/: 0}; { "插入原生接口文档(调用 DLL)"; function(id){ winform.editPrompt.selText = " [原生类型](https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/datatype.html.md) [结构体](https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/struct.html.md) [声明原生 API](https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/api.html.md) [原生回调函数](https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/callback.html.md) " }; 0}; { "快速检索相关文档"; function(id){ var q = winform.editPrompt.selText; if(!#q) q = winform.editPrompt.text; if(#q){ import inet.url; raw.execute("https://www.aardio.com/zh-cn/doc/?q="+inet.url.encode(q)); } }; #owner.text==0 ? 1/*_MF_GRAYED*/ : 0}; { /*分隔线*/ }; { '导入对话'; function(id){ import fsys.dlg; var path = fsys.dlg.open("*.jsonl|*.jsonl|*.json|*.json||","自 jsonl 或 json 文件导入对话"); if(!path) return; wb.importChatMessages(path); }; winform.btnSend.text == "停止" ? 1/*_MF_GRAYED*/ : 0}; { "导出对话"; function(id){ import fsys.dlg; var path = fsys.dlg.save("*.jsonl|*.jsonl|*.json|*.json||","导出对话到 *.jsonl"); if(!path) return; var file,err = io.file(path,"w+b"); if(!file) return winform.msgboxErr(err); var chatMessage = wb.chatMessage; if(..string.endWith(path,".jsonl",true)){ for(i=1;#chatMessage;1){ var msg = chatMessage[i] var line = JSON.stringify(msg,false,false); line = string.crlf(line,"") file.write(line,'\r\n'); } } else { file.write((JSON.stringifyArray(chatMessage,true,false))); } file.close(); }; #wb.chatMessage<3 ? 1/*_MF_GRAYED*/ : 0}; { "编辑对话"; function(id){ ide.createProcess("~\example\WebUI\web.view\OtherApps\json-editor.aardio",{ _JSON_EDITOR_TEXT = JSON.stringifyArray(wb.chatMessage,true,false); }); }; #wb.chatMessage<3 ? 1/*_MF_GRAYED*/ : 0}; { /*分隔线*/ }; (win.getStyleEx(winform.hwnd, 8/*_WS_EX_TOPMOST*/) & 8) ? ( { '取消置顶'; function(id){ win.setTopmost(winform.hwnd,false) } } ) : ( { '置顶窗口'; function(id){ win.setTopmost(winform.hwnd) } } ) } }) wb.BeforeNavigate2 = function( pDisp, url, flags, targetFrame, postData, headers, cancel ) { if(..string.match(url,"\.<@@jsonl@>|<@@json@>$") && io.exist(url) ){ winform.setTimeout( function(){ wb.importChatMessages(url) } ); } else { if(url[1]=='a'# && ..string.startsWith(url,"about:") ){ url = ..string.replace(url,"^about\:<<\.\./>+>|/(|||)" ,"https://www.aardio.com/zh-cn/doc/\1"); } url = ..string.replace(url,"^(<@https://www.aardio.com/zh-cn/doc/library-guide/std/@>)(.+)$" ,function(dir,path){ path = ..string.replace(path,"\.html$",".md"); var fullpath = io.joinpath("~/doc/library-guide/std",path); if(!..io.exist(fullpath)){ return "https://www.aardio.com/zh-cn/doc/library-reference/"+path } } ); url = ..string.replace(url,"\.md$",".html"); url = string.replace(url,"^<@https://www.aardio.com/zh-cn/doc/library-reference/@>(.+?)?\.html$" ,function(libPath){ libPath = ..string.replace(libPath,"/","."); libPath = ..string.replace(libPath,"\.\.","."); var libPath2 = ..io.libpath(libPath) if(!libPath2){ return "https://www.aardio.com/zh-cn/doc/?q="+libPath; } libPath2 = ..fsys.path.relative(libPath2,"~/lib",false); libPath2 = ..string.replace(libPath2,"\.aardio$",""); libPath2 = ..string.replace(libPath2,"\\","/"); return "https://www.aardio.com/zh-cn/doc/library-reference/"+libPath2+".html"; } ) ..raw.execute(url); } return url, flags, targetFrame, postData, headers, true; } winform.onDropFiles = function(files){ var path = files[1]; if(..string.match(path,"\.<@@jsonl@>|<@@json@>$")){ wb.importChatMessages(path); } } wb.importChatMessages = function(path){ var json = string.load(path); if(!#json){ return winform.msgboxErr("无效对话数据") } var messages; if(..string.endsWith(path,".jsonl",true)){ messages = JSON.ndParse(json); } else { messages = JSON.parse(json); } if(!#messages){ return winform.msgboxErr("无效对话数据") } wb.clear(); wb.aiSearched = null; for(i=1;#messages;1){ var msg = messages[i]; if(type(msg.content)!="string"){ if(msg[["role"]]=="assistant"){ var f = msg[["tool_calls"]][[1]][["function"]]; if(f[["name"]] && f[["arguments"]]){ wb.assistant("正在调用工具 "+f.name+ ',参数:\r\n\r\n```javascript\r\n' + f.arguments + '\r\n```\r\n\r\n') continue; } } if(msg[["role"]]!="user"){ resetMessages(); return winform.msgboxErr("对话数据格式错误,role 字段的值不是 user 则 content 字段必须是字符串!") } elseif(!..table.isArrayLike(msg.content)){ return winform.msgboxErr("对话数据格式错误,content 字段只能是字符串或数组!") } } if(msg[["role"]]=="system"){ wb.system(msg.content) } elseif(msg[["role"]]=="user"){ wb.prompt(msg.content) } elseif(msg[["role"]]=="assistant"){ var content = msg.content || ""; if(winform.chkFix.checked){ content = ide.aifix.markdown(content,true); } wb.assistant(content) wb.assistant(null) } else { //resetMessages(); //return winform.msgboxErr("对话数据格式错误,role 字段必须是 system,user,assistant 之一。") } } } global.onError = function( err,over ){ if(!over){ import debug; var stack = debug.traceback(,"调用栈",3); err = string.concat(err,stack); } if( _STUDIO_INVOKED ) { import win; win.msgboxErr(err); } } winform.spinMaxTokens.buddy = winform.editMaxTokens; winform.spinMaxTokens.setRange(1,1024*16); winform.spinMaxTokens.pos = config.maxTokens || 2048; winform.spinMaxTokens.inc = 1024; winform.beforeDestroy = function(){ config.maxTokens = winform.spinMaxTokens.pos; } winform.btnSnap.oncommand = function(id,event){ import fsys.dlg; import web.form.snap; if(key.getState("CTRL")){ winform.btnSnap.disabled = true; web.form.snap(wb,function(bmp){ var hbmp = bmp.copyHandle(); win.clip.writeBitmap(hbmp,true); return true; } ); } else{ var path = fsys.dlg.save("*.jpg|*.jpg","AI 聊天助手 - 保存对话截图",,winform); winform.editPrompt.setFocus(); if(!path) return; winform.btnSnap.disabled = true; web.form.snap(wb,path); winform.editPrompt.setFocus(); } wb.doScript(`document.documentElement.scrollTop = document.documentElement.scrollHeight + 50;`); thread.delay(1000); winform.btnSnap.disabled = false; winform.editPrompt.setFocus(); } winform.btnSearch.oncommand = function(id,event){ var frmSearch = win.form(text="AI 联网搜索接口配置";right=852;bottom=434;bgcolor=0xFFFFFF;border="dialog frame";max=false) frmSearch.add( btnSave={cls="plus";text="保存 / 测试搜索";left=657;top=389;right=811;bottom=419;align="left";color=0x3C3C3C;db=1;dr=1;font=LOGFONT(h=-13);iconColor=5724159;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0AA';notify=1;textPadding={left=25};z=15}; chkExa={cls="plus";text="启用 exa.ai 联网搜索";left=23;top=15;right=242;bottom=51;align="left";dl=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=2}; chkTavily={cls="plus";text="启用 Tavily 联网搜索";left=23;top=111;right=234;bottom=142;align="left";db=1;dl=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=3}; editSearchCount={cls="edit";text="2";left=114;top=218;right=409;bottom=250;dl=1;dt=1;edge=1;multiline=1;num=1;z=7}; editExaExcludeDomains={cls="edit";left=114;top=303;right=409;bottom=335;db=1;dl=1;dt=1;edge=1;multiline=1;z=11}; editExaIncludeDomains={cls="edit";left=114;top=261;right=409;bottom=293;dl=1;dt=1;edge=1;multiline=1;z=9}; editExaKey={cls="edit";left=114;top=64;right=409;bottom=96;dl=1;dt=1;edge=1;multiline=1;password=1;z=1}; editQuery={cls="edit";left=46;top=391;right=630;bottom=423;db=1;dl=1;dr=1;edge=1;multiline=1;z=13}; editResult={cls="richedit";left=427;top=23;right=836;bottom=378;db=1;dr=1;dt=1;edge=1;hscroll=1;link=1;multiline=1;vscroll=1;z=14}; editTavilyKey={cls="edit";left=114;top=152;right=409;bottom=184;db=1;dl=1;edge=1;multiline=1;password=1;z=5}; lbTavilyKey={cls="static";text="API key:";left=15;top=157;right=101;bottom=184;align="right";db=1;dl=1;transparent=1;z=6}; lbSearchCount={cls="static";text="最大网页数:";left=-11;top=226;right=101;bottom=253;align="right";dl=1;dt=1;transparent=1;z=8}; lbExaKey={cls="static";text="API key:";left=14;top=71;right=101;bottom=98;align="right";dl=1;dt=1;transparent=1;z=4}; lbExcludeDomains={cls="static";text="排除域名:";left=34;top=311;right=101;bottom=338;align="right";db=1;dl=1;dt=1;transparent=1;z=12}; lbIncludeDomains={cls="static";text="搜索域名:";left=34;top=268;right=101;bottom=295;align="right";dl=1;dt=1;transparent=1;z=10}; lnkExa={cls="syslink";text='exa.ai';left=269;top=25;right=412;bottom=43;bgcolor=0xFFFFFF;dl=1;dt=1;z=16}; lnkTavily={cls="syslink";text='tavily.com';left=261;top=117;right=404;bottom=135;bgcolor=0xFFFFFF;db=1;dl=1;z=17} ) frmSearch.editResult.text = /* - 每次清除上下文之前仅联网搜索一次。 大模型的上下文是有限的,注意首次的提示词对搜索友好。 - 已限定使用对 aardio 问题效果最好的 exa.ai 站内搜索, 改用 ImTip 自带的 AI 助手可修改选项或使用其他 AI 接口。 - 使用 aardio 官方 AI 接口自动忽略此功能, F1 键沉浸式 AI 助手自动忽略此功能,"""选中代码""" 上下文问答自动忽略此功能。 aardio 的 AI 接口已自带更快效果更好 aardio 知识库。 立即开通 aardio 专业版 AI 接口,更快更好更智能! https://aardio.com/vip/ */ frmSearch.chkTavily.oncommand = function(id,event){ if(frmSearch.chkTavily.checked){ frmSearch.chkExa.checked = false; } } frmSearch.chkExa.oncommand = function(id,event){ if(frmSearch.chkExa.checked){ frmSearch.chkTavily.checked = false; } } if(!config.search){ config.search = {} }; if(config.search.mode = "exa"){ frmSearch.chkExa.checked = true; } elseif(config.search.mode = "tavily"){ frmSearch.chkTavily.checked = true; } if(!config.search.exa){ config.search.exa = {}; } frmSearch.editSearchCount.text = config.search.exa.count; if(#config.search.exa.includeDomains){ frmSearch.editExaIncludeDomains.text = string.join(config.search.exa.includeDomains,","); } else { frmSearch.editExaIncludeDomains.text = "www.aardio.com" } if(#config.search.exa.excludeDomains){ frmSearch.editExaExcludeDomains.text = string.join(config.search.exa.excludeDomains,","); } frmSearch.editExaKey.text = config.search.exa[["key"]]; frmSearch.editTavilyKey.text = config.search.tavily[["key"]]; frmSearch.editExaIncludeDomains.text = "www.aardio.com"; frmSearch.editExaIncludeDomains.disabled = true; frmSearch.editExaExcludeDomains.disabled = true; frmSearch.btnSave.oncommand = function(id,event){ config.search.exa = { key = frmSearch.editExaKey.text; count = tonumber(frmSearch.editSearchCount.text); includeDomains = #frmSearch.editExaIncludeDomains.text ? string.split(frmSearch.editExaIncludeDomains.text,",") : null; excludeDomains = #frmSearch.editExaExcludeDomains.text ? string.split(frmSearch.editExaExcludeDomains.text,",") : null; } if(#config.search.exa.includeDomains){ if(#config.search.exa.excludeDomains){ config.search.exa.excludeDomains = null; frmSearch.editExaExcludeDomains.showErrorTip("指定包含域名以后,不能再指定排除域名"); return; } } config.search.tavily = { key = frmSearch.editTavilyKey.text; } if(frmSearch.chkExa.checked){ config.search.mode = "exa" } elseif(frmSearch.chkTavily.checked){ config.search.mode = "tavily" } else { config.search.mode = null; return; } if(!#frmSearch.editQuery.text){ frmSearch.editQuery.showInfoTip("已保存设置,但测试搜索的内容为空"); return ; } frmSearch.btnSave.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'} frmSearch.editResult.text = "" thread.invoke( function(frmSearch,searchConfig){ import web.rest.jsonClient; if(frmSearch.chkExa.checked){ //导入 Exa 索接口 var exaClient = web.rest.jsonClient(); exaClient.setHeaders({ "x-api-key": frmSearch.editExaKey.text} ) var exa = exaClient.api("https://api.exa.ai/"); //搜索 var searchData,err = exa.search({ query:"aardio 编程语言: " + frmSearch.editQuery.text, contents ={ text = true}, includeDomains = searchConfig.exa.includeDomains, numResults: tonumber( frmSearch.editSearchCount.text ), type:"keyword" //一般 keyword 搜索就够了(价格低一些) }) var ret = searchData[["results"]] if(!frmSearch.editResult.valid) return; if(ret){ frmSearch.editResult.print(ret); } elseif(err){ frmSearch.editResult.print(err); } } elseif(frmSearch.chkTavily.checked){ var http = web.rest.jsonClient(); http.setAuthToken(frmSearch.editTavilyKey.text); var tavily = http.api("https://api.tavily.com"); //搜索 var searchData,err = tavily.search({ query = "aardio 编程语言: " + frmSearch.editQuery.text, includeDomains = searchConfig.exa.includeDomains, max_results = tonumber( frmSearch.editSearchCount.text ); //返回的搜索结果数量,不必要太多,前两三条就可以了 }) //将搜索结果添加到系统提示词 var ret = searchData[["results"]] if(!frmSearch.editResult.valid) return; if(ret){ frmSearch.editResult.print(ret); } elseif(err){ frmSearch.editResult.print(err); } } frmSearch.btnSave.disabledText = null; },frmSearch,config.search) } var chkStyle = { color={ active=0xFF00FF00; default=0xFF000000; disabled=0xEE666666; hover=0xFFFF0000 }; checked={ iconText='\uF14A' }; } frmSearch.btnSave.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF6D6D6D; hover=0xFFFF0000 } iconColor = { disabled=0xFF6D6D6D; } }) frmSearch.chkTavily.skin(chkStyle); frmSearch.chkExa.skin(chkStyle); frmSearch.doModal(winform); winform.editPrompt.setFocus(); } //按钮外观样式 winform.btnClear.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) //按钮外观样式 winform.btnSend.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) //按钮外观样式 winform.btnSetting.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) winform.btnSearch.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) winform.btnCopy.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) winform.btnSnap.skin({ color={ active=0xFF00FF00; default=0xFF3C3C3C; disabled=0xFF999999; hover=0xFFFF0000 } }) winform.chkFix.skin({ color={ active=0xFF00FF00; default=0xFF000000; disabled=0xFF999999; hover=0xFFFF0000 }; checked={ iconText='\uF14A'; color={ active=0xFF00FF00; default=0xFF000000; disabled=0xFF999999; hover=0xFFFF0000 }; } }) if(config.itemNames){ winform.cmbAgent.items = config.itemNames; } if(config.selItem){ winform.cmbAgent.selIndex = config.selItem; } winform.cmbAgent.onOk = function(){ if(winform.cmbAgent.selIndex){ var configItem = config.itemData[winform.cmbAgent.selIndex]; if(configItem){ aiChatConfig = ..table.assign({},configItem); if(!wb.started()){ if( !( _ASK_AI_SYSTEMP_PROMPT && string.find(winform.editPrompt.text,`<"""选中代码""">|<"""报错信息""">`) )){ resetMessages(); } } } } } import win.ui.simpleWindow; win.ui.simpleWindow(winform); winform.bkPrompt.directDrawBackgroundOnly(); winform.show(); win.loopMessage();