У меня есть серверное приложение Blazor. С помощью этого приложения я «общаюсь» с MistralAI. Эта конечная точка REST API имеет токен потоковой передачи, который вы можете установить, что позволит ей передавать вам ответы в потоковом режиме.
У меня в приложении есть сервис, который связывается с MistralAI для этого разговора. Я реализовал шаблон Observer, чтобы иметь возможность обновлять свою страницу .RAZOR всякий раз, когда получаю ответ от MistralAI:
МойService.cs:
public class MyService {
private List<Reader> readers = new List<Reader>();
private AIResponse response;
public async Task<AIResponse> myTask(){
Reader newReader = new MyPage();
readers.Add(newReader);
// Send requests to and receive responses from MistralAI
addWord(receivedJSONString);
notifyReaders();
}
public void notifyReaders()
{
foreach (var reader in readers) {
reader.updateWords(response);
}
}
public void addWord(AIResponse newResponse)
{
response = newResponse;
}
}
public interface Reader {
void updateResponses(AIResponse newResponse);
}
MyPage.razor
@implements Reader
<div>
@foreach(var newResponse in responses){
@newResponse
}
</div>
@code {
private List<AIResponse> responses { get; set; } = new List<AIResponse>();
private async Task QueryAI(){
AIResponse streamingAIResponse = await myService.myTask(someString);
}
public void updateResponses(AIResponse newResponse){
responses.Add(newResponse);
Console.Write(newResponse);
StateHasChanged();
}
}
Я могу успешно получать ответы на свою страницу .RAZOR, я вижу их в консоли, когда Console.Write
, но не могу обновить пользовательский интерфейс новым контентом; Я продолжаю получать ошибку The render handle is not yet assigned
.
Итак, как я могу использовать StateHasChanged()
из моего реализованного интерфейса?
Или как я могу обновить свой пользовательский интерфейс новым контентом?
ОБНОВЛЕНИЯ:
@code {
[Inject]
public IMyService myService { get; set; }
private String queryString { get; set; } = null;
private List<AIResponse> responses { get; set; } = new List<AIResponse>();
private Conversation conversation { get; set; }
private ElementReference aiQuery;
protected override void OnInitialized()
{
if (conversation == null)
{
conversation = new onversation();
conversation.messages = new List<Message>();
}
}
private async Task QueryAI()
{
Message message = new Message("user", queryString);
conversation.messages.Add(message);
StreamingQueryResponse streamingAIResponse = await myService.getStreamingAIResponseAsync(conversation);
}
public void updateWords(StreamingQueryResponse newResponse)
{
responses.Add(newResponse);
Console.Write(newResponse);
}
}
@MrCakaShaunCurtis Я добавил раздел @code
своего компонента в свой пост. Я не уверен, как можно вручную создать компонент. у моего Index.razor
есть подкомпонент, который, в свою очередь, имеет подкомпонент, в котором MyPage.razor является еще одним подкомпонентом. Кроме того, я не могу дождаться возвращения MyService, поскольку он постоянно получает обновления от веб-сервиса MistralAI. Мне нужно отправить ответы моему компоненту по мере его получения моей службой.
Судя по жизненному циклу блазора, можно попробовать уведомить компоненты об изменениях сервиса через обработку событий.
Так сделать нельзя — создайте компонент вручную. Это работа рендерера.
Reader newReader = new MyPage();
Вам необходимо разделить службу, которая получает сообщения, и компонент пользовательского интерфейса, который их отображает.
Для этого ваш сервис предоставляет NewMessage event
, на который подписывается любой, кто хочет получать уведомления — в данном случае ваши компоненты отображения.
Это шаблон уведомлений Blazor. Здесь вы можете увидеть пример реализации, который вы можете адаптировать под свои нужды.
Но я сделал это. Я реализовал шаблон наблюдателя, и теперь моя служба успешно отправляет обновления моему компоненту пользовательского интерфейса. Все, что мне нужно сделать, это указать пользовательскому интерфейсу обновиться.
Кроме того, все три статьи, на которые вы ссылаетесь, связаны с взаимодействием компонентов. Это не то, чего я хочу. Мне нужно взаимодействие между сервисом и компонентом.
[Вежливо] Они актуальны. Это Компонент -> Сервис -> Компонент. Они демонстрируют, как реализовать уведомления от службы к компоненту, а также шаблон для использования в компоненте. Как показано в ответе Бенни.
Я ценю ваш вклад и переключился на решение @Bennyboy1973.
Шаблон наблюдателя обычно имеет службу с событием, на которое вы подписываетесь на своей потребляющей странице.
Следующий код не работает, но должен содержать все элементы для понимания связи сервис/страница:
-сервис выполняет все взаимодействия с API. Он устанавливает асинхронный вызов, и каждый раз, когда поступает новая информация о сообщении, он запускает событие.
- страница или компонент подписывается на событие службы, добавляя обработчик событий для обработки каждого сообщения и (что немаловажно) удаляет его в Dispose
.
МистральService.cs
namespace MyApp.Code
{
public class MistralService
{
public event Action<string> OnMessageReceived;
public async Task StartStreamingAsync(string apiKey)
{
using var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "mistral.api/endpoint");
request.Headers.Add("Authorization", $"Bearer {apiKey}");
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
OnMessageReceived?.Invoke(line);
}
}
}
}
ПотреблениеPage.razor
@using MyApp.Code
@inject MistralService MS
<h3>ConsumingPage</h3>
<button @onclick=StartStreaming>Start Streaming</button>
<div>@streamedText</div>
@code {
string streamedText = "";
protected override void OnInitialized()
{
MS.OnMessageReceived += HandleMessage;
}
async Task StartStreaming()
{
await MS.StartStreamingAsync("theAPIkey");
}
async void HandleMessage(string Message)
{
streamedText += Message + "<br/>";
await InvokeAsync(StateHasChanged);
}
public void Dispose()
{
MS.OnMessageReceived -= HandleMessage;
}
}
Не забудьте добавить MistralService в свой сервис в Program.cs
:
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddScoped<MistralService>();
Это сработало отлично, спасибо! Однако есть одна особенность Dispose()
: его никогда не вызывают. Есть идеи, почему?
Dispose следует вызывать, когда вы закончите работу со страницей, например, когда вы перемещаетесь по ней или закрываете ее. Или, если вы используете службу в компоненте, когда компонент удаляется из пользовательского интерфейса.
Вы ждете
myService.MyTask
вMyPage
, а определения нет. Вы определяетеupdateResponses
иQueryAI
, но не показываете, как они называются. В сообщении об ошибке говорится, что вы пытаетесь визуализировать компонент до того, как он будет прикреплен к RenderTree средством рендеринга. Вы пытаетесь создать компонент вручную?