添加项目文件。

This commit is contained in:
SpecialX
2025-05-23 19:03:00 +08:00
parent 6fa7679fd3
commit d36fef2bbb
185 changed files with 13413 additions and 0 deletions

View 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();
}
}
}