函数/New-SenseNova图像.ps1
|
function New-SenseNova图像 { <# .SYNOPSIS 通过商汤日日新开放平台调用 SenseNova U1 系列图片创作模型生成图像。 .DESCRIPTION 走日日新独立图像接口(非 Chat Completions): - 无参考图 → POST /v1/images/generations 文生图 - 有参考图 → POST /v1/images/edits 图片编辑(输入图 + 编辑指令),第一张为主编辑图 密钥、基础地址与模型以 DPAPI 加密记住。默认调用官网 (https://token.sensenova.cn/v1)的旗舰模型(默认 SenseNova U1 Pro, 当前处于邀测阶段,未开通时请用 -模型 换成 sensenova-u1.5-lite 等已开放模型)。 接口要点(U1 家族统一协议): - 图像输出统一以 base64 返回(response_format 强制 b64_json),避开 url 模式的 24 小时临时链接失效问题。 - 参考图仅支持公网 http/https URL 或带 "data:image/*;base64," 前缀的 Data-URL;本地文件自动转 Data-URL。 - n 仅支持 1;水印参数显式传(默认无水印,公测期免费,后续转付费特性)。 .PARAMETER 提示词 图像描述或编辑指令。与 -提示文件 二选一。 有参考图时提示词为编辑指令:描述期望的最终画面,尽量保留未指定修改的主体元素。 .PARAMETER 提示文件 提示词文本文件路径。与 -提示词 二选一。 .PARAMETER 密钥 交互式输入密钥开关。指定此开关时会提示输入。 平台密钥在 https://platform.sensenova.cn/console/keys 创建。 .PARAMETER 密钥值 API 令牌明文。未指定时查找已记住的值。 .PARAMETER 基础地址 日日新 Base URL。默认官网 https://token.sensenova.cn/v1。 .PARAMETER 模型 日日新平台模型 ID。默认 SenseNova U1 Pro。 其他可选(按平台开放情况):sensenova-u1.5-lite、sensenova-u1-fast 等。 注意:U1 Pro 处于邀测阶段,若调用返回 404 类错误说明当前密钥未开通该模型权限, 请通过 -模型 更换为已开通的模型。 .PARAMETER 参考图 参考图像路径或 URL(可多张,第一张为主编辑图)。 本地文件自动读取并编码为 Base64 Data-URL(带 data:image/*;base64, 前缀); URL 直接透传(须为公网可访问的 http/https 链接)。 有参考图时走 /v1/images/edits,否则走 /v1/images/generations。 .PARAMETER 尺寸 生成尺寸,支持: - auto(默认,自动适配;编辑模式下自动适配主图) - 分辨率档位:2K / 4K(平台官方建议档位,U1 Pro 原生支持更高分辨率) - 精确像素 WxH(如 2048x2048):宽高均须为 32 的倍数、介于 512~4096,且长短边之比 ≤3 本地会校验精确像素规则,避免浪费调用。 .PARAMETER 输出格式 png | jpeg | webp。默认 png。仅控制图片格式,不影响返回形式(始终 base64)。 JPEG 不支持透明背景;WebP 兼顾文件大小与透明背景。 .PARAMETER 水印 添加日日新 SenseNova 官方 Logo 水印。默认关闭(公测期无水印免费)。 官方说明无水印生成后续将转为付费高级特性。 .PARAMETER 保留提示词 跳过平台的提示词自动润色优化(prompt_extend)。 默认不传此开关,由平台自动扩写优化提示词(扩写失败时自动使用原始提示词)。 适用场景:提示词已精心设计、不希望被模型改写。 指定后显式传 prompt_extend=false,避免平台未来默认值变更影响行为。 .PARAMETER 输出路径 输出文件路径。默认时间戳 PNG。 .PARAMETER 超时秒数 单次 HTTP 请求的超时秒数。默认 1800(U1 Pro 的图文交错思维链生成 需要自主完成草图-细化-着色-检查-调整全流程,耗时远超普通模型)。 网络抖动重试不受此超时限制,直到成功或服务端给出明确错误。 .EXAMPLE New-SenseNova图像 -提示词 "水彩柴犬" -密钥值 'xxx' .EXAMPLE New-SenseNova图像 -提示词 "把背景改成雪山,人物保持不变" -参考图 .\照片.png .EXAMPLE New-SenseNova图像 -提示词 "复杂信息图:神经网络发展史,含大量文字与图表" -尺寸 4K .EXAMPLE New-SenseNova图像 -提示词 "水彩柴犬" -模型 'sensenova-u1.5-lite' -尺寸 2K -输出格式 webp #> [CmdletBinding(DefaultParameterSetName = '提示词')] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = '提示词')] [ValidateNotNullOrEmpty()] [string]$提示词, [Parameter(Mandatory = $true, ParameterSetName = '提示文件')] [ValidateNotNullOrEmpty()] [string]$提示文件, [Parameter()] [switch]$密钥, [Parameter()][string]$密钥值, [Parameter()][string]$基础地址, [Parameter()][string]$模型, [Parameter()] [ValidateNotNullOrEmpty()] [string[]]$参考图, [Parameter()][string]$尺寸 = 'auto', [Parameter()] [ValidateSet('png', 'jpeg', 'webp')] [string]$输出格式 = 'png', [Parameter()] [switch]$水印, [Parameter()] [switch]$保留提示词, [Parameter()] [string]$输出路径 = ".\sensenova_$(Get-Date -Format 'yyyyMMdd_HHmmss').png", [Parameter()][int]$超时秒数 = 1800 ) $配置路径 = Join-Path $env:LOCALAPPDATA 'Image-generation-cli\AI图像生成-SenseNova配置.xml' # 提示词 if ($PSCmdlet.ParameterSetName -eq '提示文件') { $提示词 = Read-提示文件 -路径 $提示文件 } # 尺寸本地预校验(官方规则:auto / 2K / 4K / WxH,WxH 需 32 的倍数、512~4096、最大比例 3:1) if ($尺寸 -match '^\d+[xX]\d+$') { $宽度 = [int]($尺寸.Split('x', 'X')[0]) $高度 = [int]($尺寸.Split('x', 'X')[1]) if ($宽度 % 32 -ne 0 -or $高度 % 32 -ne 0) { throw "尺寸宽高须为 32 的倍数:当前 ${宽度}x${高度}(宽余 $($宽度 % 32)、高余 $($高度 % 32))。" } if ($宽度 -lt 512 -or $宽度 -gt 4096 -or $高度 -lt 512 -or $高度 -gt 4096) { throw "尺寸宽高须介于 512~4096 之间:当前 ${宽度}x${高度}。也可改用档位:2K / 4K。" } $长短边比 = [Math]::Max($宽度, $高度) / [Math]::Min($宽度, $高度) if ($长短边比 -gt 3) { throw "长短边之比须 ≤3:1(或 1:3):当前 ${宽度}x${高度}($([Math]::Round($长短边比, 2)):1)。" } } # 凭据 if ($密钥.IsPresent -and -not $密钥值) { $安全密钥 = Read-Host -Prompt '请输入日日新 API 密钥' -AsSecureString $密钥值 = [System.Net.NetworkCredential]::new([string]::Empty, $安全密钥).Password } $记住的配置 = Import-记住的配置 -配置路径 $配置路径 # 默认值:基础地址与模型(用户指定的显式参数优先;已记住的值优先于默认) if (-not $基础地址 -and -not ($记住的配置 -and $记住的配置.基础地址)) { $基础地址 = 'https://token.sensenova.cn/v1' } if (-not $MyInvocation.BoundParameters.ContainsKey('模型') -and -not ($记住的配置 -and $记住的配置.模型)) { $模型 = 'SenseNova U1 Pro' } $凭据 = Resolve-配置凭据 -参数密钥 $密钥值 -参数基础地址 $基础地址 -参数模型 $模型 ` -记住的配置 $记住的配置 -配置路径 $配置路径 # 端点选择 + 参考图:URL 直接透传;本地文件转带 MIME 前缀的 base64 Data-URL。 # 官方要求:不支持纯无前缀 base64;链接访问失败 / 内容不是图片 / base64 解码错误会被直接驳回。 $参考图URL列表 = [System.Collections.Generic.List[string]]::new() $有参考图 = [bool]$参考图 if ($有参考图) { $总数据量 = [long]0 foreach ($单张 in $参考图) { if ($单张 -match '^https?://') { $参考图URL列表.Add($单张) } else { $数据 = Get-图像数据 -来源 $单张 $mime = Get-Mime类型 -路径 $数据.文件名 if ($mime -eq 'application/octet-stream') { $mime = 'image/png' } $总数据量 += $数据.字节.Length $参考图URL列表.Add('data:' + $mime + ';base64,' + [Convert]::ToBase64String($数据.字节)) } } # Data-URL 编码后体积约为原文件的 1.33 倍,留足余量避免请求体过大被拒 if ($总数据量 -gt 83886080) { throw "参考图总大小超过 80MB 上限(当前 $([Math]::Round($总数据量 / 1MB, 1)) MB)。" } } # 端点:基础地址已带 /vN 后缀直接用,否则补 /v1 if ($凭据.基础地址 -match '/v\d+/?$') { $api基础 = $凭据.基础地址.TrimEnd('/') } else { $api基础 = $凭据.基础地址.TrimEnd('/') + '/v1' } if ($有参考图) { $端点 = "$api基础/images/edits" } else { $端点 = "$api基础/images/generations" } # 请求体(日日新图像接口) # response_format 固定 b64_json:url 模式临时链接仅 24 小时有效, # base64 直接落盘无失效风险,网络中断重试也不必重新生成(会重复计费)。 # watermark 显式传(公测期无水印免费,官方建议显式传参以防默认值变更)。 $请求体对象 = [ordered]@{ model = $凭据.模型 prompt = $提示词 n = 1 size = $尺寸 output_format = $输出格式 response_format = 'b64_json' watermark = $水印.IsPresent } if ($保留提示词.IsPresent) { $请求体对象['prompt_extend'] = $false } if ($有参考图) { $请求体对象['images'] = @(foreach ($u in $参考图URL列表) { @{ image_url = $u } }) } $请求体 = $请求体对象 | ConvertTo-Json -Depth 6 Write-Host "正在调用 $($凭据.模型) 生成图像(U1 Pro 需多步思维链,可能耗时数分钟)..." -ForegroundColor Cyan # 用 HttpClient 提交:本地参考图 base64 编码后请求体可能很大, # Invoke-RestMethod(HttpWebRequest)默认 Expect 100-continue 在大体积上传时容易被服务端断开。 # 密钥验证失败(401)时不直接失败退出:立即交互式提示输入新密钥,然后重试。 # 网络抖动(连接中断无服务端响应体)无限重试等待恢复;有响应体的明确错误直接抛出。 Add-Type -AssemblyName System.Net.Http while ($true) { $客户端 = [System.Net.Http.HttpClient]::new() $客户端.Timeout = [TimeSpan]::FromSeconds($超时秒数) $客户端.DefaultRequestHeaders.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $凭据.密钥明文) $内容 = [System.Net.Http.StringContent]::new($请求体, [System.Text.Encoding]::UTF8, 'application/json') try { $响应消息 = $客户端.PostAsync($端点, $内容).GetAwaiter().GetResult() $响应文本 = $响应消息.Content.ReadAsStringAsync().GetAwaiter().GetResult() if (-not $响应消息.IsSuccessStatusCode) { $状态码 = [int]$响应消息.StatusCode if ($状态码 -eq 401) { Write-Warning "密钥验证失败(HTTP $状态码):$响应文本" $新安全密钥 = Read-Host -Prompt '请重新输入正确的日日新 API 密钥以立即重试(留空则取消)' -AsSecureString if ($新安全密钥.Length -eq 0) { throw [System.Management.Automation.RuntimeException]"未输入密钥,请求已取消。" } $凭据.安全密钥对象 = $新安全密钥 $凭据.密钥明文 = [System.Net.NetworkCredential]::new([string]::Empty, $新安全密钥).Password $凭据.密钥来源 = '交互输入' Write-Host "使用新密钥重试..." -ForegroundColor Yellow continue } $提示信息 = '' if ($状态码 -eq 404 -and $凭据.模型 -eq 'SenseNova U1 Pro') { $提示信息 = '(Hint:404 + 邀测中的默认模型通常表示该密钥未开通 U1 Pro 权限,' + '请换模型重试,例如 -模型 sensenova-u1.5-lite)' } if ($状态码 -eq 400 -and ($响应文本 -match 'image_url|images')) { $提示信息 = '(Hint:参考图不合法。URL 须公网可访问且是图片;' + 'Base64 须带 data:image/*;base64, 前缀——本函数转本地文件为 Data-URL)' } throw [System.Management.Automation.RuntimeException]"HTTP $状态码:$响应文本 $提示信息" } $响应 = $响应文本 | ConvertFrom-Json break } catch [System.Management.Automation.MethodInvocationException] { # 连接中断时 HttpClient 常抛 TaskCanceledException,真实原因藏在内部异常链里 $详情 = Get-NetException详情 -Exception $_.Exception if ($详情 -match '^HTTP \d{3}[::]' -or $详情 -match '"error"|"code"') { # 有服务端响应体,视为明确业务错误 throw [System.Management.Automation.RuntimeException]"API 请求失败:$详情" } # 纯传输层中断(无响应体):视为网络抖动,重试 Write-Warning "连接中断(网络抖动,稍后重试):$详情" Start-Sleep -Seconds 3 continue } catch [System.Management.Automation.RuntimeException] { throw } catch { throw "API 请求失败:$($_.Exception.Message)" } finally { $内容.Dispose(); $客户端.Dispose() } } # 记住凭据 if ($凭据.密钥来源 -ne '已记住' -or $凭据.基础地址来源 -ne '已记住' -or $凭据.模型来源 -ne '已记住') { try { Save-配置 -安全密钥 $凭据.安全密钥对象 -保存基础地址 $凭据.基础地址 -保存模型 $凭据.模型 -配置路径 $配置路径 Write-Host "密钥、基础地址与模型已记住:$配置路径" -ForegroundColor DarkGray } catch { Write-Warning "配置保存失败:$($_.Exception.Message)" } } # 解析响应 $数据属性 = $响应.PSObject.Properties['data'] if (-not $数据属性 -or -not $数据属性.Value -or @($数据属性.Value).Count -eq 0) { $响应预览 = $(try { $响应 | ConvertTo-Json -Depth 6 -Compress } catch { (Out-String -InputObject $响应) }) throw "响应中没有 data 字段:$响应预览" } $图像 = @($数据属性.Value)[0] $b64属性 = $图像.PSObject.Properties['b64_json'] if (-not $b64属性 -or -not $b64属性.Value) { throw "data[0] 中没有 b64_json 字段:$($图像 | ConvertTo-Json -Depth 6 -Compress)" } $解析后输出 = Resolve-输出路径 -输出路径 $输出路径 $字节 = [Convert]::FromBase64String($b64属性.Value) [System.IO.File]::WriteAllBytes($解析后输出, $字节) # 提示改写信息 $改写属性 = $图像.PSObject.Properties['revised_prompt'] if ($改写属性 -and $改写属性.Value) { Write-Host "改写后的提示词: $($改写属性.Value)" -ForegroundColor DarkGray } Write-Host "图像已保存: $解析后输出" -ForegroundColor Green return $解析后输出 } |