aardio 文档

aardio 范例: AI 编程助手

立即开通 aardio 专业版 AI 接口,更快更好更智能!

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=5724159;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 = ..table.assign({},config);

//创建显示聊天消虑的 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.systemPromp);  

    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 projPath = ide.getProjectPath();
    if(!#projPath) return;

    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' + 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 = project + desc + '\r\n\r\n';

        var codePath = ide.getActiveDocPath();
        if(#codePath && ..string.endsWith(codePath,".aardio",true) ){
            project = project + '\r\n\r\n用户当前正在编辑的文件为: "'+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 tApiUrl = inet.url.split(aiChatConfig.url);
    if(!tApiUrl){
        wb.errorMessage(`错误的接口网址,;<a href="javascript:void(0)" onclick="javascript:external.updateApiKey()">点这里重新设置</a>`)
        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(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,"<```>|<import>|<web\.view>|<inet\.http>|<web\.rest>")){

        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 密钥错误!<a href="https://aardio.com/vip/">点这里获取密钥</a>,&nbsp;<a href="javascript:void(0)" onclick="javascript:external.updateApiKey()">点这里设置新密钥</a>`)
                }
                else {
                    wb.errorMessage(err)
                }
            }  
            elseif(!ok){
                wb.errorMessage("错误代码:" + ( client.lastStatusCode : "未知 " + (client.lastResponseString() || "")))
            }
            elseif(aiChatConfig.usage){
                var last = client.lastResponseObject()

                if(last){

                    var out = ""

                    var usage = last.usage || last["amazon-bedrock-invocationMetrics"]
                    if(usage){
                        var cTokens,pTokens,cacheTokens;
                        if(client.apiMode=="aliyun"){
                            usage = usage[["models"]][[1]];
                            cTokens = usage.output_tokens
                            pTokens = usage.input_tokens;
                        }
                        else {
                            cTokens = usage.completion_tokens || usage["outputTokenCount"]
                            pTokens = usage.prompt_tokens || usage.inputTokenCount || usage.input_tokens;
                            cacheTokens = usage.prompt_cache_hit_tokens
                        }

                        if(cTokens){
                            out = out + '回复 tokens:<code>' + ..math.size64(cTokens).format() + "</code> "
                        }

                        if(pTokens){
                            out = out + '提示 tokens:<code>' + ..math.size64(pTokens).format() + "</code> "
                        }

                        if(cacheTokens){
                            out = out + '缓存 tokens:<code>' + ..math.size64(cacheTokens).format() + "</code> "
                        } 

                        if( string.match( aiChatConfig.url,"<@@https://api.deepseek.com/v1@>" )){

                            if( aiChatConfig.model!="deepseek-reasoner"){ 
                                if( time() < time("2025/02/09 00:00:00")){
                                    out = out + '本次费用:<code>' 
                                        + math.round(((pTokens-cacheTokens)*1+cacheTokens*0.1+cTokens*2)/1000000,3) 
                                        +  " 元 </code>"   
                                }
                                else {
                                    out = out + '本次费用:<code>' 
                                        + math.round(((pTokens-cacheTokens)*2+cacheTokens*0.5+cTokens*8)/1000000,3) 
                                        +  " 元 </code>"    
                                } 
                            }
                            else {
                                out = out + '本次费用:<code>' 
                                        + math.round(((pTokens-cacheTokens)*4+cacheTokens*1+cTokens*16)/1000000,3) 
                                        +  " 元 </code>"   
                            }

                        } 
                        elseif( string.match( aiChatConfig.url,"<@@https://openrouter.ai/api/v1@>" )){
                            if( aiChatConfig.model=="anthropic/claude-3.5-sonnet"){
                                out = out + '本次费用:<code>' 
                                    + math.round((pTokens*3+cTokens*15)/1000000,3) 
                                    +  " 美元 </code>"    
                            } 
                            elseif( ..string.endsWith(aiChatConfig.model,":free") ){
                                out = out + "本次费用:<code> 0 美元 </code>"
                            }
                        } 
                        elseif( string.match( aiChatConfig.url,"<@@https://api.siliconflow.cn/v1@>" )){

                            if( aiChatConfig.model=="deepseek-ai/DeepSeek-V3"){ 
                                if( time() < time("2025/02/09 00:00:00")){
                                    out = out + '本次费用:<code>' 
                                        + math.round((pTokens*1+cTokens*2)/1000000,3) 
                                        +  " 元 </code>"   
                                }
                                else {
                                    out = out + '本次费用:<code>' 
                                        + math.round((pTokens*2+cTokens*8)/1000000,3) 
                                        +  " 元 </code>"    
                                } 
                            }
                            elseif( aiChatConfig.model == "deepseek-ai/DeepSeek-R1") {
                                out = out + '本次费用:<code>' 
                                        + math.round((pTokens*4+cTokens*16)/1000000,3) 
                                        +  " 元 </code>"   
                            }
                        } 
                    }

                    if(last.error){
                        wb.errorMessage( last.error[["message"]] || ..JSON.stringify(last.error,true,false) ) 
                    }
                    else {
                        wb.errorMessage(#out ? out : "模型未提供 token 用量")
                    } 
                }
                else { 
                    wb.errorMessage("模型未提供 token 用量")
                } 
            }

        },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();
    } 
}

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]*)(```+)<aardio>(.+?)!\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){
    var frmSetting = winform.loadForm("/aardioAgent/setting.aardio")

    if(wb.documentMode<11){
        frmSetting.chkKatex.checked = false;
        frmSetting.chkKatex.disabled = true;
    }

    if( frmSetting.doModal(winform) ){
        var configItem = config.selItem ? config.itemData[config.selItem] 
        if(configItem){
            table.assign(aiChatConfig,configItem); 

            winform.cmbAgent.items = config.itemNames;
            winform.cmbAgent.selIndex = config.selItem; 
        } 
    }

    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 知识库,生成的回复与代码质量会严重下降。
    * <a href="https://aardio.com/vip/">点此购买专业版密钥(API Key)</a> &nbsp;<a href="javascript:void(0)" onclick="javascript:external.updateApiKey()">点此设置新密钥</a> 
- 聊天界面可联网读取提示词内的网页链接,并自动转换为 Markdown 格式文本。
- 按住 `Ctrl` 键点下面的 `复制` 按钮可复制 AI 最后一次输出的代码块。
- 按住 `Ctrl` 键点下面的 <image src="data:image/x-png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAiCAYAAAAkjjtxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYcAAB2HAY/l8WUAAAFzSURBVFhH7ZY9roQwDIT3wpyFK3ABempKSi5ARUVDRUHDagpLfnYICcTWPomRRlptQvLJP0k+xz/TR/7x63qBrXULeNu2Y1mWqDHHQtnAwzAcVVUleZom+fljZQHP86ygrlw60lnAXdcpoCsjIyV1Cox0juP4xxImxXVdq3WelIoC3vf9aJpGbVzabdvKrZOkgPu+V4tbGdHOlQKWi1r6TpQfASMbODno7MXvnAy5AaPGY8fVuq7qm5BdgAGLxiQhqtT9ACWlQLsAI/UQoLGhHEdJkHB8yXFuc2BElxSrVd79cozbHJhAUL9yTJoUux3dgFEWckyaFLsh3YBTGooUKx1zYKSXFLu+eePF5pkDw3T+nkWZH3tXpeMCzKMMeEQTG8MoGYJNeUS5AMM85SGlwMJuwDCAcMtxARSXBd7Acn7IrsDcAEyF5C4CHLpurXxVWiEp4LPutzB/RKVKAUOA5t1f2lj7DiwUBP5lvcDWeoGt9QXF/vNj8+wS+wAAAABJRU5ErkJggg==" height="24"  style="display: inline-block; vertical-align: top;"> 按钮可截长屏到剪贴板。 
- 按住 `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.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<2 ? 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};     

        { /*分隔线*/ };

        (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\:<<\.\./>+>|/(<language\-reference/>|<library\-guide/>|<language\-reference/>|<guide/>)"
                    ,"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 = aiChatConfig.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='<a href="https://exa.ai/">exa.ai</a>';left=269;top=25;right=412;bottom=43;bgcolor=0xFFFFFF;dl=1;dt=1;z=16};
    lnkTavily={cls="syslink";text='<a href="https://app.tavily.com/home">tavily.com</a>';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(aiChatConfig.itemNames){
    winform.cmbAgent.items = aiChatConfig.itemNames;
}

if(aiChatConfig.selItem){
    winform.cmbAgent.selIndex = aiChatConfig.selItem;
} 

winform.cmbAgent.onOk = function(){ 
    if(winform.cmbAgent.selIndex){
        var configItem = aiChatConfig.itemData[winform.cmbAgent.selIndex];
        if(configItem){
            aiChatConfig.proxy = null;
            table.assign(aiChatConfig,configItem);  

            if(!wb.started()){
                resetMessages();
            }
        }       
    }
}

import win.ui.simpleWindow;
win.ui.simpleWindow(winform);

winform.bkPrompt.directDrawBackgroundOnly();
winform.show();

win.loopMessage();
Markdown 格式