目录Blocks 与事件监听器Blocks 结构Blocks Structure事件监听器与交互性Event Listeners and Interactivity事件监听器的类型Types of Event Listeners多数据流Multiple Data Flows函数输入列表与字典Function Input List vs Set函数返回列表与字典Function Return List vs Dict更新组件配置Updating Component Configurations保持组件值不变Not Changing a Components Value连续运行事件Running Events Consecutively将多个触发器绑定到一个函数Binding Multiple Triggers to a Function将组件值直接绑定到其他组件的函数Binding a Component Value Directly to a Function of Other Components总结上一篇Gradio.Net (https://github.com/feiyun0112/Gradio.Net)是一个开源的 .NET 库它是 Gradio 的 .NET 移植版本允许你为机器学习模型、API 或任何 C# 函数快速构建演示或 Web 应用程序无需任何 JavaScript、CSS 或 Web 开发经验Blocks 与事件监听器我们在快速入门中简要介绍了Blocks类将其作为构建自定义演示的方式。让我们深入了解一下。Blocks 结构Blocks Structure请看下面的演示using Gradio.Net; using Gradio.Net.Components; string Greet(string name) { return Hello name !; } using var demo gr.Blocks(); var name gr.Textbox(label: Name); var output gr.Textbox(label: Output Box); var greetBtn gr.Button(Greet); greetBtn.Click(fn: Greet, inputs: name, outputs: output, apiName: greet); await demo.Launch();首先注意 using var demo gr.Blocks();语句。Blocks 应用的代码将包含在其中。接下来是组件。这些组件与 Interface中使用的相同。不同之处在于组件不是传递给某个构造函数而是在 using语句之后直接创建自动添加到 Blocks 中。最后是 Click()事件监听器。事件监听器定义了应用中的数据流。在上面的示例中监听器将两个 Textbox 连接起来。Textbox name作为输入Textbox output作为 Greet方法的输出。当按钮 greetBtn被点击时就会触发这个数据流。与 Interface 一样事件监听器可以接受多个输入或输出。事件监听器与交互性Event Listeners and Interactivity在上面的示例中你会注意到可以编辑 Textboxname但不能编辑 Textboxoutput。这是因为任何作为事件监听器输入的组件都会被设置为可交互状态。而 Textboxoutput只作为输出Gradio.Net 判断它不应该是可交互的。你可以通过布尔型interactive关键字参数直接配置组件的交互性例如gr.Textbox(label: Output, interactive: true)注意如果一个 Gradio.Net 组件既不是输入也不是输出会怎样如果组件在构造时有默认值则认为它在显示内容不可交互。否则它会被渲染为可交互状态。同样可以通过指定interactive参数来覆盖默认行为。事件监听器的类型Types of Event Listeners请看下面的演示using Gradio.Net; using Gradio.Net.Components; string Welcome(string name) { return $Welcome to Gradio, {name}!; } using var demo gr.Blocks(); gr.Markdown( # Hello World! Start typing below to see the output. ); var inp gr.Textbox(placeholder: What is your name?); var outBox gr.Textbox(); inp.Change(fn: Welcome, inputs: inp, outputs: outBox); await demo.Launch();与点击触发不同welcome函数是在 Textboxinp中输入时触发的这是因为使用了Change()事件监听器。不同的组件支持不同的事件监听器。例如Video组件支持Play()事件监听器当用户点击播放时触发。有关每个组件支持的事件监听器请参阅文档。多数据流Multiple Data FlowsBlocks 应用不像 Interface 那样只限于单一数据流。请看下面的演示using Gradio.Net; using Gradio.Net.Components; double Increase(double num) { return num 1; } using var demo gr.Blocks(); var a gr.Number(label: a); var b gr.Number(label: b); using (gr.Row()) { var atob gr.Button(a b); var btoa gr.Button(b a); atob.Click(fn: Increase, inputs: a, outputs: b); btoa.Click(fn: Increase, inputs: b, outputs: a); } await demo.Launch();代码注意a可以作为b的输入反之亦然随着应用变得更复杂你会有很多连接各种组件的数据流。下面是一个多步骤演示示例其中一个模型语音转文字的输出被输入到下一个模型情感分类器using Gradio.Net; using Gradio.Net.Components; // 示例语音转文字 情感分类的多步骤流程 string SpeechToText(string speech) { // 实际应用中这里会调用ASR模型 return Transcribed: speech; } string TextToSentiment(string text) { // 实际应用中这里会调用情感分类器 return text.Contains(good) ? POSITIVE : NEGATIVE; } using var demo gr.Blocks(); var audioText gr.Textbox(label: Speech Input (text for demo)); var text gr.Textbox(label: Transcribed Text); var label gr.Textbox(label: Sentiment); using (gr.Row()) { var b1 gr.Button(Transcribe); var b2 gr.Button(Classify Sentiment); b1.Click(fn: SpeechToText, inputs: audioText, outputs: text); b2.Click(fn: TextToSentiment, inputs: text, outputs: label); } await demo.Launch();函数输入列表与字典Function Input List vs Set当事件监听器有多个输入组件时有两种方式传递数据给函数作为参数列表或作为单个字典键为组件以下是两种方式的示例using Gradio.Net; using Gradio.Net.Components; double Add(double num1, double num2) { return num1 num2; } double Sub(double num1, double num2) { return num1 - num2; } using var demo gr.Blocks(); var a gr.Number(label: a); var b gr.Number(label: b); using (gr.Row()) { var addBtn gr.Button(Add); var subBtn gr.Button(Subtract); } var c gr.Number(label: sum); // 方式1传入列表函数按参数顺序接收各输入 addBtn.Click(fn: Add, inputs: new ListComponent { a, b }, outputs: c); // 方式2同样传入列表 subBtn.Click(fn: Sub, inputs: new ListComponent { a, b }, outputs: c); await demo.Launch();在 Gradio.Net 中推荐使用方式1参数列表函数的参数按照 inputs 列表的顺序接收对应值。函数返回列表与字典Function Return List vs Dict类似地你可以以列表或字典形式返回多个输出组件的值。方式1返回元组多个值using Gradio.Net; using Gradio.Net.Components; (double, string) Eat(double food) { if (food 0) return (food - 1, full); else return (0, hungry); } using var demo gr.Blocks(); var foodBox gr.Number(value: 10, label: Food Count); var statusBox gr.Textbox(label: Status); gr.Button(Eat).Click( fn: Eat, inputs: foodBox, outputs: new ListComponent { foodBox, statusBox }); await demo.Launch();方式2返回字典跳过部分组件更新在 Gradio.Net 中可以返回DictionaryComponent, object来选择性更新某些输出组件using Gradio.Net; using Gradio.Net.Components; using var demo gr.Blocks(); var foodBox gr.Number(value: 10, label: Food Count); var statusBox gr.Textbox(label: Status); DictionaryComponent, object Eat(double food) { if (food 0) return new DictionaryComponent, object { { foodBox, food - 1 }, { statusBox, full } }; else return new DictionaryComponent, object { { statusBox, hungry } }; } gr.Button(Eat).Click( fn: Eat, inputs: foodBox, outputs: new ListComponent { foodBox, statusBox }); await demo.Launch();当没有食物时我们只更新statusBox元素跳过了foodBox的更新。字典返回在事件监听器影响许多组件时或条件性地影响输出时非常有用。注意使用字典返回时仍然需要在事件监听器中指定可能的输出。更新组件配置Updating Component Configurations事件监听器函数的返回值通常是相应输出组件的更新值。有时我们想同时更新组件的配置如可见性。这时我们返回一个新的组件实例设置想要更改的属性using Gradio.Net; using Gradio.Net.Components; Textbox ChangeTextbox(string choice) { return choice switch { short gr.Textbox(lines: 2, visible: true), long gr.Textbox(lines: 8, visible: true, value: Lorem ipsum dolor sit amet), _ gr.Textbox(visible: false) }; } using var demo gr.Blocks(); var radio gr.Radio( choices: new Liststring { short, long, none }, label: What kind of essay would you like to write?); var text gr.Textbox(lines: 2, interactive: true); radio.Change(fn: ChangeTextbox, inputs: radio, outputs: text); await demo.Launch();注意如何通过返回新的gr.Textbox()来配置 Textbox 本身。value参数仍然可以用来同时更新值和组件配置。未设置的参数将保留其先前的值。保持组件值不变Not Changing a Components Value在某些情况下你可能希望保持组件的值不变。Gradio.Net 提供了特殊函数gr.Skip()从函数中返回它将保持输出组件的值不变using Gradio.Net; using Gradio.Net.Components; using var demo gr.Blocks(); using (gr.Row()) { var clearButton gr.Button(Clear); var skipButton gr.Button(Skip); var randomButton gr.Button(Random); } var numbers new ListComponent { gr.Number(), gr.Number() }; clearButton.Click(fn: () (object?, object?)(null, null), outputs: new ListComponent(numbers)); skipButton.Click(fn: () (object, object)(gr.Skip(), gr.Skip()), outputs: new ListComponent(numbers)); randomButton.Click( fn: () (object, object)(Random.Shared.Next(0, 101), Random.Shared.Next(0, 101)), outputs: new ListComponent(numbers)); await demo.Launch();注意返回null通常会将组件值重置为空状态与返回gr.Skip()保持组件值不变的区别。如果有多个输出组件且想让所有值都保持不变可以只返回单个gr.Skip()而不需要为每个元素都返回一个 skip。连续运行事件Running Events Consecutively你还可以使用事件监听器的Then方法来连续运行事件。这会在前一个事件完成后运行下一个事件适用于分多步更新组件的情况。例如在下面的聊天机器人示例中我们首先立即用用户消息更新聊天机器人然后在模拟延迟后用计算机响应再次更新聊天机器人using Gradio.Net; using Gradio.Net.Components; using var demo gr.Blocks(); var chatbot gr.Chatbot(); var msg gr.Textbox(label: Message); var clear gr.Button(Clear); (string, ListDictionarystring, object) UserMessage(string userMessage, ListDictionarystring, object history) { history ?? new ListDictionarystring, object(); history.Add(new Dictionarystring, object { [role] user, [content] userMessage }); return (, history); } ListDictionarystring, object BotResponse(ListDictionarystring, object history) { history ?? new ListDictionarystring, object(); var responses new[] { How are you?, I love you, Im very hungry }; Thread.Sleep(1000); history.Add(new Dictionarystring, object { [role] assistant, [content] responses[Random.Shared.Next(responses.Length)] }); return history; } msg.Submit( fn: UserMessage, inputs: new ListComponent { msg, chatbot }, outputs: new ListComponent { msg, chatbot }, queue: false) .Then( fn: BotResponse, inputs: chatbot, outputs: chatbot); clear.Click( fn: () new ListDictionarystring, object(), inputs: null, outputs: chatbot, queue: false); await demo.Launch();事件监听器的.Then()方法无论前一个事件是否抛出错误都会执行后续事件。如果你只想在前一个事件成功执行后才运行后续事件可以使用.Success()方法它接受与.Then()相同的参数。反之如果只想在前一个事件失败时运行后续事件可以使用.Failure()方法这对错误处理工作流特别有用。将多个触发器绑定到一个函数Binding Multiple Triggers to a Function通常你可能希望将多个触发器绑定到同一个函数。例如你可能希望用户点击提交按钮或按下回车键时都能提交表单。可以在 Gradio.Net 中为同一个函数分别注册多个事件监听器这在功能上与gr.On的多触发器等价using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; string Greet(string name) { return Hello name !; } using var demo gr.Blocks(); var name gr.Textbox(label: Name); var output gr.Textbox(label: Output Box); var greetBtn gr.Button(Greet); // 将同一个函数绑定到多个触发器按下Enter和点击按钮 name.Submit(fn: Greet, inputs: name, outputs: output); greetBtn.Click(fn: Greet, inputs: name, outputs: output); await demo.Launch();代码你还可以不指定任何触发器函数将自动绑定到所有包含change事件的输入组件的change事件using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; using var demo gr.Blocks(); using (gr.Row()) { var num1 gr.Slider(minimum: 1, maximum: 10); var num2 gr.Slider(minimum: 1, maximum: 10); var num3 gr.Slider(minimum: 1, maximum: 10); var output gr.Number(label: Sum); gr.On( inputs: new ListComponent { num1, num2, num3 }, outputs: output, fn: (double a, double b, double c) a b c); } await demo.Launch();你可以在gr.On后跟.Then就像任何普通事件监听器一样。这个便捷方法可以帮你避免编写大量重复代码将组件值直接绑定到其他组件的函数Binding a Component Value Directly to a Function of Other Components如果你希望一个组件的值始终是其他组件值的某个函数可以使用以下简写using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; using var demo gr.Blocks(); var num1 gr.Number(); var num2 gr.Number(); var product gr.Number( value: (double a, double b) a * b, inputs: new ListComponent { num1, num2 }); await demo.Launch();这在功能上等同于using Gradio.Net; using Gradio.Net.Components; using Gradio.Net.Events; using var demo gr.Blocks(); var num1 gr.Number(); var num2 gr.Number(); var product gr.Number(); gr.On( triggers: new ListTriggerBinding { num1.Change, num2.Change }, fn: (double a, double b) a * b, inputs: new ListComponent { num1, num2 }, outputs: product); await demo.Launch();总结本章介绍了 Gradio.Net 中 Blocks 和事件监听器的核心概念Blocks 结构用 gr.Blocks()构建自定义布局组件直接在 using块内创建事件监听器通过 .Click()、.Change()、.Submit()等方法绑定事件处理函数多数据流同一个组件可以在不同数据流中既作为输入又作为输出连续事件用 .Then()链式调用实现多步骤事件多触发器用 gr.On()将多个触发器绑定到同一个函数引入地址