添加项目文件。
This commit is contained in:
196
TechHelper.Client/Pages/AI/AIDialog.razor
Normal file
196
TechHelper.Client/Pages/AI/AIDialog.razor
Normal file
@@ -0,0 +1,196 @@
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user