Files
TechHelper/TechHelper.Client/Pages/AI/AIDialog.razor
2025-05-23 19:03:00 +08:00

196 lines
6.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/ai"
@using Newtonsoft.Json
@using TechHelper.Client
@using TechHelper.Client.AI
@using TechHelper.Client.Exam
@inject IAIService AiService
@inject ISnackbar Snackbar
<MudPaper Elevation="3" Height="100%" Width="100%">
<MudContainer>
<MudAppBar Elevation="1">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => ToggleDrawer())" />
<MudText Typo="Typo.h6" Class="ml-4">AI 对话</MudText>
<MudSpacer />
<MudButton Variant="Variant.Text" Color="Color.Inherit" OnClick="ClearChat">清空对话</MudButton>
<MudIconButton Icon="@Icons.Material.Filled.Settings" Color="Color.Inherit" OnClick="@(() => IsSettingsOpen = !IsSettingsOpen)" />
</MudAppBar>
<MudDrawer @bind-Open="@_drawerOpen" Elevation="1" Variant="@DrawerVariant.Temporary">
<MudDrawerHeader>
<MudText Typo="Typo.h6">对话历史 / 设置</MudText>
</MudDrawerHeader>
<MudNavMenu>
<MudNavLink Href="/ai-chat" Icon="@Icons.Material.Filled.Chat">新对话</MudNavLink>
<MudDivider />
@* 假设这里可以显示对话历史,点击切换
@foreach (var history in ChatHistorySummaries)
{
<MudNavLink @onclick="@(() => LoadChat(history.Id))">@history.Title</MudNavLink>
}
*@
<MudExpansionPanel Text="模型设置" @bind-Expanded="@IsSettingsOpen" Class="my-2">
<MudCard>
<MudCardContent>
<MudSelect Label="模型选择" @bind-Value="SelectedModel" Variant="Variant.Filled" Margin="Margin.Dense">
@foreach (var model in Enum.GetValues<AIModelsEnum>())
{
<MudSelectItem Value="@model.GetDescription()">@model.ToString()</MudSelectItem>
}
</MudSelect>
<MudSlider @bind-Value="Temperature" Min="0.0m" Max="1.0m" Step="0.01m" T="decimal" Label="Temperature" Class="mt-4">
<ChildContent>
<MudText Typo="Typo.body2">@Temperature.ToString("F2")</MudText>
</ChildContent>
</MudSlider>
<MudNumericField @bind-Value="MaxTokens" Label="最大Token数" Min="1" Max="32000" Step="1" Variant="Variant.Filled" Margin="Margin.Dense" Class="mt-4" />
</MudCardContent>
</MudCard>
</MudExpansionPanel>
</MudNavMenu>
</MudDrawer>
<MudContainer MaxWidth="MaxWidth.Medium">
@foreach (var message in ChatMessages)
{
<MudChat Color="Color.Success" Dense="true" Elevation="0" Variant="Variant.Text" ChatPosition="@(message.Role == "user" ? ChatBubblePosition.End : ChatBubblePosition.Start)">
<MudChatHeader Name="@(message.Role == "user" ? "user" : "ai")" Time="12:46" />
<MudAvatar Size=" Size.Small">
<MudImage Src="images/toiletvisit.jpg" />
</MudAvatar>
<MudChatBubble> @message.Content </MudChatBubble>
<MudChatFooter Text="Seen at 12:46" />
</MudChat>
}
@if (IsLoading)
{
<MudPaper Class="ai-message mb-2 pa-3 loading-indicator" Elevation="1">
<MudText Typo="Typo.body2"><strong>AI</strong></MudText>
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
<MudText Typo="Typo.body2" Class="ml-2">思考中...</MudText>
</MudPaper>
}
<div @ref="messagesEndRef" class="scroll-anchor"></div> @* 滚动锚点 *@
</MudContainer>
<MudPaper Class="input-area d-flex align-center pa-3" Elevation="8" Style="position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; width: 100%;">
<MudContainer MaxWidth="MaxWidth.Medium" Class="d-flex" Style="width: 100%;">
<MudTextField @bind-Value="UserInput"
Label="输入你的消息..."
Variant="Variant.Filled"
Lines="1"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.Send"
OnAdornmentClick="SendMessage"
Class="flex-grow-1 mr-2"
Disabled="@IsLoading"
KeyDown="@HandleKeyDown" />
</MudContainer>
</MudPaper>
</MudContainer>
</MudPaper>
@code {
private bool _drawerOpen = false;
private bool IsSettingsOpen = false;
private bool IsExamCheckOpen = false;
private List<Message> ChatMessages = new List<Message>();
private string UserInput { get; set; } = "";
private bool IsLoading { get; set; } = false;
private ElementReference messagesEndRef; // 用于自动滚动到底部的引用
// AI 模型设置
private string SelectedModel { get; set; } = AIModelsEnum.GLMZ1Flash.GetDescription();
private decimal Temperature { get; set; } = 0.75m; // MudSlider 使用 decimal
private int MaxTokens { get; set; } = 1000;
// 试题检查相关
private string ExamJsonInput { get; set; } = "";
private string ExamCheckResult { get; set; } = "";
protected override void OnInitialized()
{
ChatMessages.Add(new SystemMessage("你是一个乐于助人的AI助手。"));
Snackbar.Add("欢迎使用AI对话", Severity.Info);
}
private void ToggleDrawer()
{
_drawerOpen = !_drawerOpen;
}
private void ClearChat()
{
ChatMessages.Clear();
ChatMessages.Add(new SystemMessage("你是一个乐于助人的AI助手。"));
Snackbar.Add("对话已清空。", Severity.Success);
}
private async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !e.ShiftKey)
{
await SendMessage();
}
}
private async Task SendMessage()
{
if (string.IsNullOrWhiteSpace(UserInput)) return;
IsLoading = true;
ChatMessages.Add(new UserMessage(UserInput));
var currentInput = UserInput; // 保存当前输入,因为会清空
try
{
// 构建请求消息列表不包括SystemMessage通常System Message只在API调用时作为参数传递不显示在聊天记录中
var requestMessages = new List<Message>();
requestMessages.Add(new SystemMessage("你是一个乐于助人的AI助手请简洁明了地回答问题。")); // 每次请求都包含系统提示
requestMessages.AddRange(ChatMessages.Where(m => m.Role != "system")); // 添加除系统消息外的所有历史对话
var request = new ChatCompletionRequest
{
Model = SelectedModel,
Messages = requestMessages,
Temperature = (float)Temperature, // MudSlider返回decimal需要转换为float
MaxTokens = MaxTokens
};
var response = await AiService.CallGLM(UserInput, AIConfiguration.ExamAnsConfig);
if (!string.IsNullOrEmpty(response))
{
// var exam = ExamParser.ParseExamJson(response);
ChatMessages.Add(new AssistantMessage(response));
}
}
catch (HttpRequestException httpEx)
{
ChatMessages.Add(new AssistantMessage($"API 请求失败: {httpEx.Message}. 请检查API Key和网络连接。"));
Snackbar.Add($"API 请求失败: {httpEx.Message}", Severity.Error);
Console.WriteLine($"HTTP Request Error: {httpEx.Message}");
}
catch (Exception ex)
{
ChatMessages.Add(new AssistantMessage($"发生错误: {ex.Message}"));
Snackbar.Add($"发生错误: {ex.Message}", Severity.Error);
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
IsLoading = false;
UserInput = ""; // 清空输入框
StateHasChanged();
}
}
}