| File: Blocks\UIActionBlock.cs | Web Access |
| Project: src\aspnetcore\src\Components\AI\src\Microsoft.AspNetCore.Components.AI.csproj (Microsoft.AspNetCore.Components.AI) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.AI; namespace Microsoft.AspNetCore.Components.AI; /// <summary> /// Represents a function call that must execute in the UI before the conversation can continue. /// </summary> /// <remarks> /// Register the corresponding function with /// <see cref="UIAgentOptions.RegisterUIAction(AIFunction)"/>. Renderers can call /// <see cref="InvokeAsync(CancellationToken)"/> to execute it in the current UI circuit. /// </remarks> /// <example> /// <code> /// <BlockRenderer TBlock="UIActionBlock" Context="action"> /// <button @onclick="() => action.InvokeAsync()">Run</button> /// </BlockRenderer> /// </code> /// </example> public class UIActionBlock : ContentBlock { private readonly AIFunction _function; private readonly TaskCompletionSource<AIContent> _resultSource = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly object _invocationLock = new(); private Task? _invocation; internal UIActionBlock(AIFunction function, FunctionCallContent call) { _function = function; Call = call; } /// <summary> /// Gets the function call requested by the model. /// </summary> public FunctionCallContent Call { get; } /// <summary> /// Gets the registered UI action name. /// </summary> public string ToolName => Call.Name; /// <summary> /// Gets the function result after the action completes. /// </summary> public FunctionResultContent? Result { get; private set; } /// <summary> /// Gets a value indicating whether the action completed successfully. /// </summary> public bool IsComplete => Result is not null; /// <summary> /// Executes the registered function once using the arguments supplied by the model. /// </summary> /// <param name="cancellationToken"> /// A token that cancels the action when this call starts it. Subsequent calls return the /// existing invocation task and do not replace its cancellation token. /// </param> /// <returns>A task that completes when the action has finished.</returns> public Task InvokeAsync(CancellationToken cancellationToken = default) { lock (_invocationLock) { _invocation ??= InvokeCoreAsync(cancellationToken); return _invocation; } } internal Task<AIContent> GetResultAsync(CancellationToken cancellationToken) => _resultSource.Task.WaitAsync(cancellationToken); private async Task InvokeCoreAsync(CancellationToken cancellationToken) { try { var arguments = Call.Arguments is not null ? new AIFunctionArguments(Call.Arguments) : null; var result = await _function.InvokeAsync(arguments, cancellationToken); Result = new FunctionResultContent(Call.CallId ?? Id, result); NotifyChanged(); _resultSource.TrySetResult(Result); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { _resultSource.TrySetCanceled(cancellationToken); throw; } catch (Exception exception) { _resultSource.TrySetException(exception); throw; } } }