# aardio 范例: ChatGPT Images 2.0 文生图

```aardio
import crypt;
import inet.http;
import win.ui.simpleWindow;
import fonts.fontAwesome;
import web.rest.jsonClient;
import gdip.webp;
import win.ui;
/*DSG{{*/
var winform = win.form(text="ChatGPT Images 2.0 文生图";right=1150;bottom=700;bgcolor=0xF5F5F5;border="none")
winform.add(
btnCopy={cls="plus";text="复制图像 / 图像文件";left=921;top=631;right=1110;bottom=680;align="left";bgcolor=0x27AE60;border={radius=6};color=0xFFFFFF;db=1;disabled=1;dr=1;font=LOGFONT(h=-14;weight=600);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=17;top=1}};iconText='\uF0EA';notify=1;textPadding={left=37};z=27};
btnGenerate={cls="plus";text="生成图像";left=742;top=631;right=902;bottom=680;align="left";bgcolor=0x3498DB;border={radius=6};color=0xFFFFFF;db=1;dr=1;font=LOGFONT(h=-15;weight=600);iconStyle={align="left";font=LOGFONT(h=-16;name='FontAwesome');padding={left=17;top=1}};iconText='\uF1FC';notify=1;textPadding={left=37};z=26};
btnOpenKeyUrl={cls="plus";text="获取 Key";left=980;top=78;right=1104;bottom=101;align="left";color=0x3498DB;dr=1;dt=1;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome')};iconText='\uF08E';notify=1;textPadding={left=18};z=23};
comboBackground={cls="combobox";left=745;top=451;right=1105;bottom=476;db=1;dr=1;edge=1;items={"auto","opaque","transparent"};mode="dropdownlist";z=20};
comboFormat={cls="combobox";left=745;top=390;right=1105;bottom=415;db=1;dr=1;edge=1;items={"png","jpeg","webp"};mode="dropdownlist";z=18};
comboQuality={cls="combobox";left=745;top=329;right=1105;bottom=354;db=1;dr=1;dt=1;edge=1;items={"auto","low","medium","high"};mode="dropdownlist";z=16};
comboSize={cls="combobox";left=745;top=268;right=1105;bottom=293;dr=1;dt=1;edge=1;items={"auto","1024x1024","1536x1024","1024x1536"};mode="dropdownlist";z=14};
editApiKey={cls="edit";left=745;top=104;right=1105;bottom=129;dr=1;dt=1;edge=1;password=1;z=8};
editApiUrl={cls="edit";left=746;top=158;right=1106;bottom=183;dr=1;dt=1;edge=1;z=28};
editN={cls="edit";text="1";left=745;top=512;right=862;bottom=537;db=1;dr=1;edge=1;z=22};
editPrompt={cls="edit";left=30;top=565;right=710;bottom=680;autohscroll=false;bgcolor=0xFFFFFF;db=1;dl=1;dr=1;edge=1;multiline=1;vscroll=1;z=11};
editProxy={cls="edit";left=746;top=214;right=1106;bottom=239;dr=1;dt=1;edge=1;z=12};
labelApiKey={cls="bkplus";text="API Key：";left=745;top=80;right=930;bottom=100;align="left";color=0x666666;dr=1;dt=1;z=6};
labelApiUrl={cls="bkplus";text="接口地址：";left=745;top=132;right=930;bottom=152;align="left";color=0x666666;dr=1;dt=1;z=29};
labelBackground={cls="bkplus";text="背景 (Background)：";left=745;top=427;right=930;bottom=447;align="left";color=0x666666;db=1;dr=1;z=19};
labelFormat={cls="bkplus";text="输出格式 (Format)：";left=745;top=366;right=930;bottom=386;align="left";color=0x666666;db=1;dr=1;z=17};
labelN={cls="bkplus";text="张数 (N)：";left=745;top=488;right=930;bottom=508;align="left";color=0x666666;db=1;dr=1;z=21};
labelPrompt={cls="bkplus";text="提示词 (Prompt)：";left=30;top=540;right=200;bottom=560;align="left";color=0x999999;db=1;dl=1;font=LOGFONT(h=-13;weight=600);z=10};
labelProxy={cls="bkplus";text="代理服务器（可选）：";left=745;top=189;right=930;bottom=209;align="left";color=0x666666;dr=1;dt=1;z=9};
labelQuality={cls="bkplus";text="质量 (Quality)：";left=745;top=305;right=930;bottom=325;align="left";color=0x666666;dr=1;dt=1;z=15};
labelSettings={cls="bkplus";text="OpenAI 图像生成设置";left=745;top=51;right=940;bottom=71;align="left";color=0x999999;dr=1;dt=1;font=LOGFONT(h=-13;weight=600);z=5};
labelSize={cls="bkplus";text="尺寸 (Size)：";left=745;top=244;right=930;bottom=264;align="left";color=0x666666;dr=1;dt=1;z=13};
labelTip={cls="plus";left=767;top=556;right=1069;bottom=607;border={color=0x008000;radius=16;width=1};db=1;dr=1;hide=1;z=24};
picturebox={cls="plus";text="请在下方输入提示词生成图像";left=30;top=57;right=710;bottom=500;border={color=0xEEEEEE;radius=8;width=1};color=0x999999;db=1;dl=1;dr=1;dt=1;font=LOGFONT(h=-16);iconStyle={font=LOGFONT(h=-60;name='FontAwesome')};iconText='\uF03E';notify=1;repeat="scale";textPadding={top=100};z=3};
progressBar={cls="plus";left=812;top=222;right=1023;bottom=433;bgcolor=0xECF0F1;border={radius=3};db=1;dr=1;dt=1;forecolor=0x3498DB;hide=1;z=25};
settingsGroup={cls="bkplus";left=730;top=57;right=1120;bottom=612;bgcolor=0xFFFFFF;border={color=0x000000};db=1;dr=1;dt=1;z=1};
titleBar={cls="bkplus";left=0;top=0;right=1150;bottom=46;bgcolor=0x2C3E50;dl=1;dr=1;dt=1;z=2};
titleIcon={cls="bkplus";text='\uF03E';left=356;top=6;right=396;bottom=42;color=0x3498DB;dl=1;dt=1;font=LOGFONT(h=-20;name='FontAwesome');z=7};
titleText={cls="bkplus";text="ChatGPT Images 2.0  文生图";left=16;top=10;right=350;bottom=34;color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-16;weight=700);z=4}
)
/*}}*/

const OPENAI_IMAGE_MODEL = "gpt-image-2";

import fsys.table;
var configImage = fsys.table(io.appData("aardio/example/AI/images/OpenAI2.table"),,{
	edit = "text";
	comboSize = "1024x1024";
	comboQuality = "auto";
	comboFormat = "png";
	comboBackground = "auto";
	editPrompt = `A cinematic photo of a tiny astronaut riding a corgi on Mars, dramatic sunset, ultra detailed, 35mm lens.`;
});

winform.bindConfig( configImage,{
	edit = "text"; 
	combobox = "selText"; 
} );

winform.editPrompt.setPadding(5,5,5,5); 
winform.progressBar.setPieRange(0,100);
winform.progressBar.orphanWindow(true);

var tempImageDir = io.tmpname();
io.createDir(tempImageDir);

winform.lastImagePath = null;
winform.lastImageExt = "png";

winform.showTip = function(str){
    winform.labelTip.text = str;
    winform.labelTip.show(true);
    winform.setTimeout(
        function(){
            winform.labelTip.show(false);
        },5000
    );
}

winform.setBusy = function(busy){
    if(busy){
        winform.btnGenerate.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'};
        winform.btnCopy.disabled = true;
        winform.progressBar.show(true);
        winform.progressBar.startProgress();
    }
    else{
        winform.btnGenerate.disabledText = null;
        winform.progressBar.stopProgress();
        winform.progressBar.show(false);
    }
}

winform.btnGenerate.oncommand = function(){
    var prompt = string.trim(winform.editPrompt.text);
    if(!#prompt){
        return winform.msgbox("请输入提示词。","提示","warn");
    }

    var apiKey = string.trim(winform.editApiKey.text);
    if(!#apiKey){
        return winform.msgbox("请填写 API Key。","缺少 API Key","warn");
    }
    
    import string.escape2;
    apiKey = string.escape2(apiKey);
    
    var size = winform.comboSize.selText || "1024x1024";
    var quality = winform.comboQuality.selText || "auto";
    var outputFormat = winform.comboFormat.selText || "png";
    var background = winform.comboBackground.selText || "auto";
    var imageCount = tonumber(winform.editN.text) || 1;
    if(imageCount < 1) imageCount = 1;
    if(imageCount > 4) imageCount = 4;

    if(background == "transparent" && outputFormat == "jpeg"){
        outputFormat = "png";
        winform.comboFormat.selText = "png";
        winform.showTip("透明背景不适合 JPEG，已自动切换为 PNG。");
    }

    winform.picturebox.background = null;
    winform.picturebox.foreground = null;
    winform.picturebox.iconText = '\uF03E';
    winform.picturebox.text = "正在连接 OpenAI 图像模型...";
    winform.text = "OpenAI gpt-image-2 文生图 - 生成中";
    winform.btnCopy.text = "复制图像 / 图像文件";
    winform.btnCopy.iconText = '\uF0EA';
    winform.lastImagePath = null;
    winform.lastImageExt = outputFormat;
    winform.setBusy(true);

    thread.invoke(
        function(winform, apiKey, prompt, size, quality, outputFormat, background, imageCount, tempImageDir, modelName){
            import web.rest.jsonClient;
            import crypt;
            import inet.http;

            var http = web.rest.jsonClient(,winform.editProxy.text);

            http.setAuthToken(apiKey);
            http.setTimeouts(15000,300000,300000);

            var requestData = {
                model = modelName;
                prompt = prompt;
                n = imageCount;
                size = size;
                quality = quality;
                output_format = outputFormat;
                background = background;
            };

            if(outputFormat == "jpeg" || outputFormat == "webp"){
                requestData.output_compression = 95;
            }

            winform.picturebox.text = "正在生成图像，请稍候...";
            
            var url = winform.editApiUrl.text;
            var api = http.api(url);
            var resultData,err = api.images.generations(requestData);

            var item = resultData[["data"]][[1]];
            var base64Data = item[["b64_json"]] || item[["b64"]];
            var imageUrl = item[["url"]];
            var revisedPrompt = item[["revised_prompt"]];

            if(base64Data){
                var bytes = crypt.decodeBin(base64Data);
                var ext = outputFormat;
                if(ext == "jpeg") ext = "jpg";
                var filename = time.format("%Y%m%d_%H%M%S") + "." + ext;
                var imagePath = io.joinpath(tempImageDir,filename);
                string.save(imagePath,bytes);

                winform.picturebox.background = imagePath;
                winform.picturebox.text = null;
                winform.picturebox.iconText = null;
                winform.btnCopy.disabled = false;
                winform.text = "OpenAI gpt-image-2 文生图 - 生成成功";
                winform.lastImagePath = imagePath;
                winform.lastImageExt = ext;

                if(revisedPrompt){
                    winform.showTip("已生成图像，双击图像可放大预览。OpenAI 返回了优化后的提示词。");
                }
                else{
                    winform.showTip("已生成图像，双击图像可放大预览。");
                }
            }
            elseif(imageUrl){
                var ext = outputFormat;
                if(ext == "jpeg") ext = "jpg";
                var filename = time.format("%Y%m%d_%H%M%S") + "." + ext;
                var imagePath = io.joinpath(tempImageDir,filename);
                var bytes,downErr = http.httpGet(imageUrl);
                if(bytes){
                    string.save(imagePath,bytes);
                    winform.picturebox.background = imagePath;
                    winform.picturebox.text = null;
                    winform.picturebox.iconText = null;
                    winform.btnCopy.disabled = false;
                    winform.text = "OpenAI gpt-image-2 文生图 - 生成成功";
                    winform.lastImagePath = imagePath;
                    winform.lastImageExt = ext;
                    winform.showTip("已生成图像，双击图像可放大预览。");
                }
                else{
                    winform.picturebox.text = "下载图像失败";
                    winform.picturebox.iconText = '\uF057';
                    winform.msgboxErr(downErr || "下载图像失败。","错误");
                }
            }
            else{
                var rawText = http.lastResponseString();
                var msg = err || resultData[["error"]][["message"]] || resultData[["message"]] || rawText || "未知错误";
                winform.picturebox.text = "生成失败";
                winform.picturebox.iconText = '\uF057';
                winform.msgboxErr(msg,"OpenAI API 请求失败");
            }

            winform.setBusy(false);
        },winform,apiKey,prompt,size,quality,outputFormat,background,imageCount,tempImageDir,OPENAI_IMAGE_MODEL
    );
}

winform.btnCopy.oncommand = function(){
    var imagePath = winform.lastImagePath;
    if(!imagePath || !io.exist(imagePath)){
        return winform.showTip("请先生成图像。");
    }

    if(winform.btnCopy.text == "打开图像文件"){
        raw.explore(imagePath);
        winform.showTip("已打开图像所在位置。");
        winform.btnCopy.text = "复制图像 / 图像文件";
        winform.btnCopy.iconText = '\uF0EA';
    }
    else{
        import win.clip.bitmap;
        win.clip.bitmap.writeHtml(imagePath);

        import win.clip.file;
        win.clip.file.write(imagePath,"copy",false);
        winform.btnCopy.text = "打开图像文件";
        winform.btnCopy.iconText = '\uF1C5';
        winform.showTip("已复制图像与图像文件。");
    }
}

winform.picturebox.onMouseDoubleClick = function(wParam,lParam){
    var imagePath = winform.lastImagePath;
    if(!imagePath || !io.exist(imagePath)){
        return winform.msgboxErr("请先生成图像。","错误");
    }

    var frmPreview = win.form(text="图像预览";right=757;bottom=466;bgcolor=0xFFFFFF;border="none")
    frmPreview.add(
    btnClose={cls="close";text="×";left=725;top=3;right=753;bottom=31;bgcolor=0xCC8B94;dr=1;dt=1;font=LOGFONT(h=-21);notify=1;z=2};
    picturebox={cls="plus";left=0;top=0;right=758;bottom=467;bgcolor=0xFFFFFF;db=1;dl=1;dr=1;dt=1;foreRepeat="scale";notify=1;repeat="scale";z=1}
    )

    frmPreview.onActivateApp = function(activated){ if(!activated) frmPreview.close(); }
    frmPreview.onCancel = function(){ frmPreview.close(); }
    frmPreview.picturebox.onMouseDoubleClick = function(){ frmPreview.close(); }
    frmPreview.picturebox.foreground = imagePath;
    frmPreview.fullscreen();
    frmPreview.btnClose.oncommand = function(){ frmPreview.close(); }
    frmPreview.btnClose.show(true);
}

winform.btnOpenKeyUrl.oncommand = function(){
    raw.execute("https://fenno.qnaigc.com/purchase?tab=subscription");
}

winform.onDestroy = function(){
    import fsys;
    fsys.delete(tempImageDir);
}

winform.btnGenerate.skin({
    background={
        default=0xFF3498DB;
        hover=0xFF2980B9;
        active=0xFF1F618D;
        disabled=0xFFBDC3C7;
    };
    color={
        default=0xFFFFFFFF;
        disabled=0xFF7F8C8D;
    }
});

winform.btnCopy.skin({
    background={
        default=0xFF27AE60;
        hover=0xFF229954;
        active=0xFF1E8449;
        disabled=0xFFBDC3C7;
    };
    color={
        default=0xFFFFFFFF;
        disabled=0xFF7F8C8D;
    }
});

winform.btnOpenKeyUrl.skin({
    color={
        default=0xFF3498DB;
        hover=0xFF2980B9;
        active=0xFF1F618D;
    }
});

win.ui.simpleWindow(winform);
winform.show();
win.loopMessage();
```