wxd 4 weeks ago
parent
commit
9feb9e09d9
  1. 67
      src/Shentun.Peis.Application.Contracts/AIMessages/DeepModel.cs
  2. 109
      src/Shentun.Peis.Application/AIMessages/AIMessageAppService.cs
  3. 210
      src/Shentun.Peis.Domain/TextFormatter.cs
  4. 188
      src/Shentun.Peis.HttpApi.Host/Controllers/AiMessageWsController.cs
  5. 12
      src/Shentun.Peis.HttpApi.Host/PeisHttpApiHostModule.cs

67
src/Shentun.Peis.Application.Contracts/AIMessages/DeepModel.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Shentun.Peis.AIMessages
{
//如果好用,请收藏地址,帮忙分享。
public class Delta
{
/// <summary>
/// 保持
/// </summary>
public string content { get; set; }
/// <summary>
///
/// </summary>
public string reasoning_content { get; set; }
}
public class ChoicesItem
{
/// <summary>
///
/// </summary>
public int index { get; set; }
/// <summary>
///
/// </summary>
public Delta delta { get; set; }
/// <summary>
///
/// </summary>
public string logprobs { get; set; }
/// <summary>
///
/// </summary>
public string finish_reason { get; set; }
}
public class Root
{
/// <summary>
///
/// </summary>
public string id { get; set; }
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public int created { get; set; }
/// <summary>
///
/// </summary>
public string model { get; set; }
/// <summary>
///
/// </summary>
public string system_fingerprint { get; set; }
/// <summary>
///
/// </summary>
public List<ChoicesItem> choices { get; set; }
}
}

109
src/Shentun.Peis.Application/AIMessages/AIMessageAppService.cs

@ -1,6 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using Azure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Shentun.Peis.Enums;
using Shentun.Peis.Models;
using System;
@ -134,108 +137,6 @@ namespace Shentun.Peis.AIMessages
return messageDto;
}
///// <summary>
///// 获取Ai回复内容 流式返回
///// </summary>
///// <param name="input"></param>
///// <returns></returns>
///// <exception cref="UserFriendlyException"></exception>
//[HttpPost("api/app/AIMessage/GetAIMessageResultStream")]
//public async Task<Stream> GetAIMessageResultStreamAsync(GetAIMessageResultInputDto input)
//{
// var messageDto = new GetAIMessageResultDto();
// if (string.IsNullOrWhiteSpace(input.Message))
// {
// throw new UserFriendlyException("请求内容不能为空");
// }
// var thirdInterface = await _thirdInterfaceRepository.FirstOrDefaultAsync(f => f.ThirdInterfaceType == ThirdInterfaceTypeFlag.WebAI);
// if (thirdInterface == null)
// {
// throw new UserFriendlyException("未配置第三方AI接口");
// }
// if (thirdInterface.IsActive != 'Y')
// {
// throw new UserFriendlyException("该接口已禁用");
// }
// var parmValue = thirdInterface.ParmValue;
// var configurationBuilder = new ConfigurationBuilder()
// .AddJsonStream(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(parmValue)));
// var config = configurationBuilder.Build();
// var apiBaseAddress = config.GetSection("Interface").GetSection("BaseAddress").Value;
// var apiKey = config.GetSection("Interface").GetSection("ApiKey").Value;
// var aiType = config.GetSection("Interface").GetSection("AIType").Value;
// var modelValue = config.GetSection("Interface").GetSection("ModelValue").Value;
// if (aiType == AITypeFlag.DeepSeek)
// {
// using (HttpClient client = new HttpClient())
// {
// client.BaseAddress = new Uri(apiBaseAddress);
// // 设置API密钥或其他认证信息(如果有的话)
// client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
// //client.DefaultRequestHeaders.Add("Accept", "text/html");
// try
// {
// var requestBody = new
// {
// model = modelValue,
// messages = new[] { new { role = "user", content = input.Message } }
// //response_format = "html"
// };
// var response = await client.PostAsJsonAsync("chat/completions", requestBody);
// var result = await response.Content.ReadFromJsonAsync<DeepSeekResponse>();
// string data = result.Choices.First().Message.Content;
// //string dataHtml = data.Replace("### ", "").Replace("---", "").Replace("-", "");
// //var dataHtmlList = dataHtml.Split("**", StringSplitOptions.RemoveEmptyEntries).ToList();
// //StringBuilder stringBuilder = new StringBuilder();
// //foreach (var item in dataHtmlList)
// //{
// // var sindex = dataHtmlList.IndexOf(item) + 1;
// // if (sindex > 1)
// // {
// // if (sindex % 2 == 0)
// // {
// // stringBuilder.Append("<strong>" + item);
// // }
// // else
// // {
// // stringBuilder.Append("</strong>" + item);
// // }
// // }
// // else
// // {
// // stringBuilder.Append(item);
// // }
// //}
// //messageDto.Result = stringBuilder.ToString();
// string dataHtml = data.Replace("### ", "").Replace("---", "").Replace("-", "").Replace("**", "");
// messageDto.Result = dataHtml;
// }
// catch (HttpRequestException e)
// {
// throw new UserFriendlyException($"获取异常:{e.Message}");
// }
// }
// }
// else
// {
// throw new UserFriendlyException("AI接口类型不正确");
// }
// return messageDto;
//}
}
}

210
src/Shentun.Peis.Domain/TextFormatter.cs

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shentun.Peis
{
public class TextFormatter
{
public static string FormatStreamingText(string text, int consoleWidth = 80)
{
if (string.IsNullOrEmpty(text))
return text;
// 1. 处理Markdown风格的格式
text = ProcessMarkdown(text);
// 2. 自动换行
text = WordWrap(text, consoleWidth);
// 3. 处理代码块
text = ProcessCodeBlocks(text);
// 4. 处理列表
text = ProcessLists(text);
//text = text.Replace("\n\n","").Replace("\n","");
return text;
}
private static string ProcessMarkdown(string text)
{
// 处理粗体 - Markdown: **text** 或 __text__ → HTML: <strong>text</strong>
text = System.Text.RegularExpressions.Regex.Replace(
text, @"\*\*(.*?)\*\*", "<strong>$1</strong>");
text = System.Text.RegularExpressions.Regex.Replace(
text, @"__(.*?)__", "<strong>$1</strong>");
// 处理斜体 - Markdown: *text* 或 _text_ → HTML: <em>text</em>
text = System.Text.RegularExpressions.Regex.Replace(
text, @"\*(.*?)\*", "<em>$1</em>");
text = System.Text.RegularExpressions.Regex.Replace(
text, @"_(.*?)_", "<em>$1</em>");
// 处理标题
text = System.Text.RegularExpressions.Regex.Replace(
text, @"^### (.*)$", "<h3>$1</h3>",
System.Text.RegularExpressions.RegexOptions.Multiline);
text = System.Text.RegularExpressions.Regex.Replace(
text, @"^## (.*)$", "<h2>$1</h2>",
System.Text.RegularExpressions.RegexOptions.Multiline);
text = System.Text.RegularExpressions.Regex.Replace(
text, @"^# (.*)$", "<h1>$1</h1>",
System.Text.RegularExpressions.RegexOptions.Multiline);
return text;
}
private static string WordWrap(string text, int maxWidth)
{
var lines = text.Split("\n", StringSplitOptions.None);
var result = new List<string>();
foreach (var line in lines)
{
if (line.Length <= maxWidth)
{
result.Add(line);
continue;
}
var words = line.Split(' ');
var currentLine = new StringBuilder();
foreach (var word in words)
{
if (currentLine.Length + word.Length + 1 > maxWidth)
{
result.Add(currentLine.ToString());
currentLine.Clear();
}
if (currentLine.Length > 0)
currentLine.Append(' ');
currentLine.Append(word);
}
if (currentLine.Length > 0)
result.Add(currentLine.ToString());
}
return string.Join("<br>", result);
}
private static string ProcessCodeBlocks(string text)
{
// 简单的代码块处理
var lines = text.Split('\n');
var result = new List<string>();
bool inCodeBlock = false;
foreach (var line in lines)
{
if (line.Trim().StartsWith("```"))
{
inCodeBlock = !inCodeBlock;
result.Add(new string('=', 60));
continue;
}
if (inCodeBlock)
{
result.Add($" {line}");
}
else
{
result.Add(line);
}
}
return string.Join("\n", result);
}
private static string ProcessLists(string text)
{
var lines = text.Split('\n');
var result = new List<string>();
bool inUnorderedList = false;
bool inOrderedList = false;
var orderedListStart = 0;
foreach (var line in lines)
{
// 处理无序列表项 (*, -, +)
var unorderedMatch = System.Text.RegularExpressions.Regex.Match(line, @"^[\*\-+]\s+(.+)");
if (unorderedMatch.Success)
{
if (!inUnorderedList)
{
// 开始无序列表
if (inOrderedList)
{
result.Add("</ol>");
inOrderedList = false;
}
result.Add("<ul>");
inUnorderedList = true;
}
result.Add($"<li>{unorderedMatch.Groups[1].Value}</li>");
continue;
}
// 处理有序列表项 (1., 2., 等)
var orderedMatch = System.Text.RegularExpressions.Regex.Match(line, @"^(\d+)\.\s+(.+)");
if (orderedMatch.Success)
{
if (!inOrderedList)
{
// 开始有序列表
if (inUnorderedList)
{
result.Add("</ul>");
inUnorderedList = false;
}
result.Add("<ol>");
inOrderedList = true;
orderedListStart = int.Parse(orderedMatch.Groups[1].Value);
}
result.Add($"<li>{orderedMatch.Groups[2].Value}</li>");
continue;
}
// 如果不是列表项,关闭之前的列表
if (inUnorderedList)
{
result.Add("</ul>");
inUnorderedList = false;
}
if (inOrderedList)
{
result.Add("</ol>");
inOrderedList = false;
}
// 添加普通行
result.Add(line);
}
// 处理末尾未关闭的列表
if (inUnorderedList)
{
result.Add("</ul>");
}
if (inOrderedList)
{
result.Add("</ol>");
}
return string.Join("\n", result);
}
}
}

188
src/Shentun.Peis.HttpApi.Host/Controllers/AiMessageWsController.cs

@ -6,64 +6,164 @@ using System.Threading;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Shentun.Peis.Models;
using Volo.Abp.Domain.Repositories;
using Microsoft.Extensions.Configuration;
using Shentun.Peis.AIMessages;
using Shentun.Peis.Enums;
using System.IO;
using System.Net.Http;
using Volo.Abp;
using Newtonsoft.Json;
using System.Linq;
namespace Shentun.Peis.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AiMessageWsController : ControllerBase
{
[HttpGet]
public IAsyncEnumerable<string> GetData()
private readonly IRepository<ThirdInterface, Guid> _thirdInterfaceRepository;
public AiMessageWsController(
IRepository<ThirdInterface, Guid> thirdInterfaceRepository
)
{
return GenerateDataAsync();
_thirdInterfaceRepository = thirdInterfaceRepository;
// 配置TLS1.2加密协议
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Tls12;
}
private async IAsyncEnumerable<string> GenerateDataAsync()
/// <summary>
/// 获取Ai回复内容
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="UserFriendlyException"></exception>
[HttpPost("api/app/AiMessageWs/GetAIMessageResult")]
public async Task GetAIMessageResultAsync(GetAIMessageResultInputDto input)
{
for (int i = 0; i < 100; i++)
Response.ContentType = "text/event-stream";
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Connection", "keep-alive");
if (string.IsNullOrWhiteSpace(input.Message))
{
throw new UserFriendlyException("请求内容不能为空");
}
var thirdInterface = await _thirdInterfaceRepository.FirstOrDefaultAsync(f => f.ThirdInterfaceType == ThirdInterfaceTypeFlag.WebAI);
if (thirdInterface == null)
{
throw new UserFriendlyException("未配置第三方AI接口");
}
if (thirdInterface.IsActive != 'Y')
{
throw new UserFriendlyException("该接口已禁用");
}
var parmValue = thirdInterface.ParmValue;
var configurationBuilder = new ConfigurationBuilder()
.AddJsonStream(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(parmValue)));
var config = configurationBuilder.Build();
var apiBaseAddress = config.GetSection("Interface").GetSection("BaseAddress").Value;
var apiKey = config.GetSection("Interface").GetSection("ApiKey").Value;
var aiType = config.GetSection("Interface").GetSection("AIType").Value;
var modelValue = config.GetSection("Interface").GetSection("ModelValue").Value;
if (aiType == AITypeFlag.DeepSeek)
{
await Task.Delay(100); // 模拟延迟
yield return $"Data {i}";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiBaseAddress);
// 设置API密钥或其他认证信息(如果有的话)
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
//client.DefaultRequestHeaders.Add("Accept", "text/html");
try
{
var requestBody = new
{
model = "deepseek-reasoner",
messages = new[] { new { role = "user", content = input.Message } },
stream = true
};
var content = new StringContent(JsonConvert.SerializeObject(requestBody), Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.deepseek.com/v1/chat/completions")
{
Content = content
};
using var response = await client.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new System.IO.StreamReader(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (line?.StartsWith("data: ") == true)
{
var s1 = line[6..];
try
{
var streamResponse = JsonConvert.DeserializeObject<Root>(s1);
if (streamResponse?.choices?[0]?.delta?.content != null)
{
var contentPiece = streamResponse.choices[0].delta.content;
// 实时输出并格式化
contentPiece = FormatAndDisplay(contentPiece);
await Response.WriteAsync($"data: {contentPiece}\n\n");
await Response.Body.FlushAsync();
}
}
catch (Exception ex) { }
if (HttpContext.RequestAborted.IsCancellationRequested)
break;
}
}
await Response.WriteAsync("data: [DONE]\n\n");
await Response.Body.FlushAsync();
}
catch (HttpRequestException e)
{
throw new UserFriendlyException($"获取异常:{e.Message}");
}
}
}
else
{
throw new UserFriendlyException("AI接口类型不正确");
}
}
//public async Task HandleWebSocket()
//{
// if (HttpContext.WebSockets.IsWebSocketRequest)
// {
// using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
// await ProcessDeepSeekStream(webSocket);
// }
// else
// {
// HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
// }
//}
//private async Task ProcessDeepSeekStream(WebSocket webSocket)
//{
// // 调用deepseek API
// var stream = await GetDeepSeekStream();
// foreach (var chunk in stream)
// {
// var buffer = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chunk));
// await webSocket.SendAsync(
// new ArraySegment<byte>(buffer),
// WebSocketMessageType.Text,
// true,
// CancellationToken.None);
// }
// await webSocket.CloseAsync(
// WebSocketCloseStatus.NormalClosure,
// "Stream completed",
// CancellationToken.None);
//}
private string FormatAndDisplay(string fullText)
{
// 应用格式化规则
var formattedText = TextFormatter.FormatStreamingText(fullText);
return formattedText;
}
}
}

12
src/Shentun.Peis.HttpApi.Host/PeisHttpApiHostModule.cs

@ -463,6 +463,16 @@ public class PeisHttpApiHostModule : AbpModule
.AllowAnyMethod()
.AllowCredentials();
});
//options.AddDefaultPolicy(builder =>
// {
// builder
// .AllowAnyOrigin()
// .AllowAnyHeader()
// .AllowAnyMethod();
// });
});
@ -606,7 +616,7 @@ public class PeisHttpApiHostModule : AbpModule
RequestPath = configuration["PacsVirtualPath:RequestPath"]
});
app.UseRouting();
app.UseCors();

Loading…
Cancel
Save