196 lines
6.8 KiB
Plaintext
196 lines
6.8 KiB
Plaintext
@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();
|
||
}
|
||
}
|
||
|
||
} |