我尝试使用AI修改,各种报错;目前最接近“能用”的一版在power shell进行调用测试时,发给AI的内容会变成几个乱码的问号
//css_ref System.Windows.Forms.dll
//css_ref System.Drawing.dll
//css_ref Microsoft.Web.WebView2.Core.dll
//css_ref System.Runtime.Serialization.dll
//css_ref System.Xml.dll
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Drawing;
using Microsoft.Web.WebView2.Core;
public static void Exec(Quicker.Public.IStepContext context)
{
if (System.Threading.Thread.CurrentThread.GetApartmentState() != System.Threading.ApartmentState.STA)
{
MessageBox.Show("配置错误:\n请将模块【执行线程】设置为 【后台线程 (STA独立线程) / staLongRun】", "DeepSeek Relay");
return;
}
string inputParam = "";
try { inputParam = context.GetVarValue("input") as string; } catch { }
if (inputParam != null)
{
inputParam = inputParam.Trim().ToLower();
}
else
{
inputParam = "";
}
if (inputParam == "shutdown" || inputParam == "stop")
{
ShutdownRemoteService();
return;
}
bool isSilent = inputParam == "silent";
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
DeepSeekForm form = new DeepSeekForm(isSilent);
Application.Run(form);
}
private static void ShutdownRemoteService()
{
try
{
var request = WebRequest.Create("http://127.0.0.1:55555/shutdown");
request.Timeout = 2000;
using(var response = request.GetResponse()) { }
MessageBox.Show("已发送停止指令。", "DeepSeek Relay");
}
catch { MessageBox.Show("连接服务失败。", "错误"); }
}
public class DeepSeekForm : Form
{
private readonly bool _isDebugMode;
private const string TargetUrl = "https://chat.deepseek.com/";
private const int ServerPort = 55555;
private readonly string _userDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "DeepSeek_Quicker_Data");
private readonly string _settingsFile;
private const string IconUrl = "https://files.getquicker.net/_icons/A79F41249974B799C815B80CF149D5C04CA4DDBE.png";
private CoreWebView2Environment _env;
private CoreWebView2Controller _controller;
private CoreWebView2 _coreWebView;
private bool _isInitializing = false;
private TextBox _txtLog;
private Button _btnToggle;
private Button _btnRestart;
private Label _lblStatus;
private Panel _rightPanel;
private Panel _webPanel;
private NotifyIcon _notifyIcon;
private ContextMenuStrip _trayMenu;
private ToolStripMenuItem _itemAutoNewTopic;
private HttpListener _httpListener;
private HttpListenerContext _currentContext;
private bool _isCurrentRequestStreaming;
private StringBuilder _networkBuffer = new StringBuilder();
private List<string> _citations = new List<string>();
private TaskCompletionSource<bool> _pageLoadTcs = new TaskCompletionSource<bool>();
private int _thinkIndex = -1;
private int _responseIndex = -1;
private bool _isThinkingOpen = false;
private bool _hasResponseStarted = false;
private string _lastEventType = "";
private bool _isHiddenMode = false;
private bool _enableAutoNewTopic = true;
public DeepSeekForm(bool isSilent)
{
_isDebugMode = !isSilent;
_settingsFile = Path.Combine(_userDataFolder, "settings.json");
LoadSettings();
InitializeComponent();
InitializeSystemTray();
this.Load += async (s, e) => {
await LoadCustomIconAsync();
await InitializeWebViewSequenceAsync();
};
this.Resize += (s, e) => {
if (!_isHiddenMode) ResizeWebView();
};
StartHttpServer();
}
private void LoadSettings()
{
try {
if (File.Exists(_settingsFile)) {
string json = File.ReadAllText(_settingsFile);
if (json.Contains("\"enableAutoNewTopic\": false")) _enableAutoNewTopic = false;
else _enableAutoNewTopic = true;
} else {
_enableAutoNewTopic = true;
}
} catch { _enableAutoNewTopic = true; }
}
private void SaveSettings()
{
try {
if (!Directory.Exists(_userDataFolder)) Directory.CreateDirectory(_userDataFolder);
string json = string.Format("{{\"enableAutoNewTopic\": {0}}}", _enableAutoNewTopic ? "true" : "false");
File.WriteAllText(_settingsFile, json);
} catch { }
}
private void InitializeSystemTray()
{
_trayMenu = new ContextMenuStrip();
_itemAutoNewTopic = new ToolStripMenuItem("自动开启新话题 (清理上下文)");
_itemAutoNewTopic.CheckOnClick = true;
_itemAutoNewTopic.Checked = _enableAutoNewTopic;
_itemAutoNewTopic.Click += (s, e) => {
_enableAutoNewTopic = _itemAutoNewTopic.Checked;
SaveSettings();
Log("设置已更改: 自动开启新话题 = " + _enableAutoNewTopic.ToString());
};
_trayMenu.Items.Add(_itemAutoNewTopic);
_trayMenu.Items.Add(new ToolStripSeparator());
_trayMenu.Items.Add("显示/隐藏控制台", null, (s, e) => ToggleWindowVisibility());
_trayMenu.Items.Add("重启浏览器内核", null, async (s, e) => await RestartWebViewAsync());
_trayMenu.Items.Add(new ToolStripSeparator());
_trayMenu.Items.Add("退出服务", null, (s, e) => this.Close());
_notifyIcon = new NotifyIcon();
_notifyIcon.Text = "DeepSeek Relay Service";
_notifyIcon.Icon = SystemIcons.Application;
_notifyIcon.ContextMenuStrip = _trayMenu;
_notifyIcon.Visible = true;
_notifyIcon.DoubleClick += (s, e) => ToggleWindowVisibility();
}
private async Task LoadCustomIconAsync()
{
try {
using (var client = new WebClient())
{
byte[] data = await client.DownloadDataTaskAsync(IconUrl);
using (var ms = new MemoryStream(data))
using (var bmp = new Bitmap(ms))
{
var icon = Icon.FromHandle(bmp.GetHicon());
this.Icon = icon;
_notifyIcon.Icon = icon;
}
}
} catch { }
}
private async Task RestartWebViewAsync()
{
Log("正在重启浏览器内核...");
if (_coreWebView != null)
{
try {
_coreWebView.ProcessFailed -= OnProcessFailed;
_coreWebView.Stop();
} catch { }
}
if (_controller != null)
{
try { _controller.Close(); } catch { }
_controller = null;
}
_coreWebView = null;
_env = null;
_isInitializing = false;
await Task.Delay(1000);
await InitializeWebViewSequenceAsync();
}
private async Task InitializeWebViewSequenceAsync()
{
if (_isInitializing) return;
_isInitializing = true;
try {
Log("正在初始化 WebView2...");
_env = await CoreWebView2Environment.CreateAsync(null, _userDataFolder);
_controller = await _env.CreateCoreWebView2ControllerAsync(_webPanel.Handle);
_coreWebView = _controller.CoreWebView2;
_coreWebView.ProcessFailed += OnProcessFailed;
ResizeWebView();
await _coreWebView.AddScriptToExecuteOnDocumentCreatedAsync(GetNetworkInterceptorScript());
_coreWebView.WebMessageReceived += OnWebMessageReceived;
_coreWebView.NavigationCompleted += async (s, e) => {
if (e.IsSuccess) {
Log("页面就绪");
_pageLoadTcs.TrySetResult(true);
}
};
_pageLoadTcs = new TaskCompletionSource<bool>();
_pageLoadTcs.TrySetResult(true);
_coreWebView.Navigate(TargetUrl);
Log("WebView2 启动成功");
}
catch (Exception ex)
{
Log("初始化失败: " + ex.Message);
}
finally
{
_isInitializing = false;
}
}
private void OnProcessFailed(object sender, CoreWebView2ProcessFailedEventArgs e)
{
string reason = e.Reason.ToString();
Log("警告:浏览器进程崩溃!原因: " + reason);
this.Invoke(new Action(async () => {
Log("3秒后尝试自动恢复...");
await Task.Delay(3000);
await RestartWebViewAsync();
}));
}
private void ToggleWindowVisibility()
{
if (_isHiddenMode)
{
_isHiddenMode = false;
if (this.Left < -10000) this.CenterToScreen();
this.ShowInTaskbar = true;
this.WindowState = FormWindowState.Normal;
try {
if (_controller != null) {
_controller.IsVisible = true;
}
} catch {
Log("句柄失效,正在重建...");
Task.Run(new Action(() => {
this.Invoke(new Action(async () => await RestartWebViewAsync()));
}));
}
ResizeWebView();
this.BringToFront();
this.Activate();
}
else
{
_isHiddenMode = true;
this.Location = new Point(-32000, -32000);
}
}
private void InitializeComponent()
{
this.Text = "DeepSeek Relay (Port: " + ServerPort.ToString() + ")";
this.Size = new Size(1200, 800);
this.StartPosition = FormStartPosition.CenterScreen;
this.BackColor = Color.FromArgb(30, 30, 30);
this.FormBorderStyle = FormBorderStyle.Sizable;
if (_isDebugMode)
{
this.ShowInTaskbar = true;
_isHiddenMode = false;
}
else
{
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.Manual;
this.Location = new Point(-32000, -32000);
_isHiddenMode = true;
}
var split = new SplitContainer();
split.Dock = DockStyle.Fill;
split.Orientation = Orientation.Vertical;
split.SplitterDistance = (int)(this.Width * 0.7);
split.BackColor = Color.FromArgb(45, 45, 48);
this.Controls.Add(split);
_webPanel = new Panel();
_webPanel.Dock = DockStyle.Fill;
_webPanel.BackColor = Color.Black;
split.Panel1.Controls.Add(_webPanel);
_webPanel.Resize += (s, e) => ResizeWebView();
_rightPanel = split.Panel2;
_rightPanel.Padding = new Padding(10);
_rightPanel.BackColor = Color.FromArgb(30, 30, 30);
var btnPanel = new Panel();
btnPanel.Dock = DockStyle.Bottom;
btnPanel.Height = 80;
btnPanel.BackColor = Color.Transparent;
_btnRestart = new Button();
_btnRestart.Text = "重启浏览器内核";
_btnRestart.Height = 35;
_btnRestart.Dock = DockStyle.Top;
_btnRestart.BackColor = Color.SteelBlue;
_btnRestart.ForeColor = Color.White;
_btnRestart.FlatStyle = FlatStyle.Flat;
_btnRestart.Click += async (s, e) => await RestartWebViewAsync();
_btnToggle = new Button();
_btnToggle.Text = "停止 HTTP 服务";
_btnToggle.Height = 35;
_btnToggle.Dock = DockStyle.Bottom;
_btnToggle.BackColor = Color.IndianRed;
_btnToggle.ForeColor = Color.White;
_btnToggle.FlatStyle = FlatStyle.Flat;
_btnToggle.Click += (s, e) => ToggleService();
btnPanel.Controls.Add(_btnRestart);
btnPanel.Controls.Add(_btnToggle);
_rightPanel.Controls.Add(btnPanel);
var infoPanel = new Panel();
infoPanel.Height = 230;
infoPanel.Dock = DockStyle.Top;
var lblTitle = new Label { Text = "DeepSeek Relay", Font = new Font("Microsoft YaHei", 14, FontStyle.Bold), ForeColor = Color.CornflowerBlue, AutoSize = true, Location = new Point(0, 0) };
infoPanel.Controls.Add(lblTitle);
_lblStatus = new Label { Text = "正在运行 (V55-终极修复版)", ForeColor = Color.LightGreen, AutoSize = true, Location = new Point(0, 35) };
infoPanel.Controls.Add(_lblStatus);
int startY = 65;
AddConfigItem(infoPanel, "Base URL (API地址):", "http://127.0.0.1:" + ServerPort.ToString(), startY);
AddConfigItem(infoPanel, "Model Name (模型名称):", "deepseek-chat", startY + 50);
AddConfigItem(infoPanel, "API Key (任意填写):", "sk-any", startY + 100);
_rightPanel.Controls.Add(infoPanel);
_txtLog = new TextBox();
_txtLog.Multiline = true;
_txtLog.ScrollBars = ScrollBars.Vertical;
_txtLog.Dock = DockStyle.Fill;
_txtLog.BackColor = Color.FromArgb(45, 45, 48);
_txtLog.ForeColor = Color.Gainsboro;
_txtLog.ReadOnly = true;
_txtLog.BorderStyle = BorderStyle.FixedSingle;
var logContainer = new Panel();
logContainer.Dock = DockStyle.Fill;
logContainer.Padding = new Padding(0, 10, 0, 10);
logContainer.Controls.Add(_txtLog);
_rightPanel.Controls.Add(logContainer);
logContainer.BringToFront();
}
private void AddConfigItem(Panel panel, string title, string value, int y)
{
var lbl = new Label { Text = title, ForeColor = Color.Gray, Location = new Point(0, y), AutoSize = true, Font = new Font("Microsoft YaHei", 9) };
var txt = new TextBox {
Text = value, ReadOnly = true, BackColor = Color.FromArgb(40,40,40),
ForeColor = Color.White, Location = new Point(0, y + 20), Width = 240, BorderStyle = BorderStyle.FixedSingle
};
var btnCopy = new Button {
Text = "复制", Location = new Point(250, y + 19), Width = 50, Height = 23,
FlatStyle = FlatStyle.Flat, BackColor = Color.DimGray, ForeColor = Color.White, Font = new Font("Microsoft YaHei", 8)
};
btnCopy.FlatAppearance.BorderSize = 0;
EventHandler copyAction = (s, e) => {
try {
Clipboard.SetText(value);
string originalText = btnCopy.Text;
btnCopy.Text = "OK"; btnCopy.ForeColor = Color.LightGreen;
Task.Delay(1000).ContinueWith(_ => {
if (btnCopy.IsDisposed) return;
btnCopy.Invoke(new Action(() => { btnCopy.Text = originalText; btnCopy.ForeColor = Color.White; }));
});
} catch { }
};
btnCopy.Click += copyAction;
txt.Click += copyAction;
panel.Controls.Add(lbl); panel.Controls.Add(txt); panel.Controls.Add(btnCopy);
}
private async Task<bool> ResetToHomeAndWaitAsync()
{
Log("新对话:正在跳转回主页清理上下文...");
_pageLoadTcs = new TaskCompletionSource<bool>();
var timeoutTask = Task.Delay(60000);
this.Invoke(new Action(async () => {
try {
if (_coreWebView != null && _coreWebView.BrowserProcessId != 0) {
_coreWebView.Navigate(TargetUrl);
} else {
Log("浏览器进程无效,正在重启...");
var ignore = RestartWebViewAsync();
}
} catch (Exception ex) {
Log("导航失败: " + ex.Message);
var ignore = RestartWebViewAsync();
}
}));
var completedTask = await Task.WhenAny(_pageLoadTcs.Task, timeoutTask);
if (completedTask == _pageLoadTcs.Task) {
await Task.Delay(2000);
Log("主页环境已就绪。");
return true;
} else {
Log("⚠️ 超时:页面加载时间过长 (60s)");
return false;
}
}
private void ResizeWebView()
{
if (_controller != null && !_webPanel.IsDisposed && _webPanel.IsHandleCreated)
{
try {
if (_webPanel.Width > 0 && _webPanel.Height > 0)
{
_controller.Bounds = new Rectangle(0, 0, _webPanel.Width, _webPanel.Height);
}
} catch { }
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
StopHttpServer();
if (_notifyIcon != null) {
_notifyIcon.Visible = false;
_notifyIcon.Dispose();
}
base.OnFormClosing(e);
}
private void ToggleService()
{
if (_httpListener != null && _httpListener.IsListening) {
StopHttpServer(); Log("服务已停止");
_btnToggle.Text = "启动 HTTP 服务"; _btnToggle.BackColor = Color.SeaGreen;
_lblStatus.Text = "已停止"; _lblStatus.ForeColor = Color.Gray;
} else {
StartHttpServer(); Log("服务已启动");
_btnToggle.Text = "停止 HTTP 服务"; _btnToggle.BackColor = Color.IndianRed;
_lblStatus.Text = "正在运行"; _lblStatus.ForeColor = Color.LightGreen;
}
}
private void StartHttpServer()
{
try {
if (_httpListener != null) StopHttpServer();
_httpListener = new HttpListener();
_httpListener.Prefixes.Add("http://127.0.0.1:" + ServerPort.ToString() + "/");
_httpListener.Start();
Task.Run(new Func<Task>(ListenLoop));
Log("HTTP 服务监听中: " + ServerPort.ToString());
} catch (Exception ex) { Log("HTTP 启动失败: " + ex.Message); }
}
private void StopHttpServer()
{
try { if (_httpListener != null) { _httpListener.Stop(); _httpListener.Close(); _httpListener = null; } } catch { }
}
private async Task ListenLoop()
{
var listener = _httpListener;
if (listener == null) return;
while (listener != null && listener.IsListening) {
try { var context = await listener.GetContextAsync(); HandleRequest(context); } catch { break; }
}
}
private async void HandleRequest(HttpListenerContext ctx)
{
string path = ctx.Request.Url.AbsolutePath.ToLower();
string method = ctx.Request.HttpMethod.ToUpper();
ctx.Response.AppendHeader("Access-Control-Allow-Origin", "*");
ctx.Response.AppendHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (method == "OPTIONS") { ctx.Response.StatusCode = 200; ctx.Response.Close(); return; }
if (path == "/shutdown") {
ResponseText(ctx, "Closing...");
this.Invoke(new Action(() => {
Task.Delay(1000).ContinueWith(_ => this.Invoke(new Action(() => this.Close())));
}));
return;
}
if ((path == "/v1/chat/completions" || path == "/chat") && method == "POST")
{
if (_currentContext != null) { ResponseText(ctx, "{\"error\": \"Busy\"}", 429, "application/json"); return; }
try {
// V55: 修复读取编码问题,强制无 BOM 的 UTF8 解析
string body = "";
using (Stream input = ctx.Request.InputStream)
{
byte[] buffer = new byte[ctx.Request.ContentLength64];
input.Read(buffer, 0, buffer.Length);
body = new UTF8Encoding(false).GetString(buffer);
}
Log("收到请求");
if (_enableAutoNewTopic)
{
int roleCount = Regex.Matches(body, "\"role\"\\s*:\\s*\"").Count;
if (roleCount <= 2)
{
bool resetSuccess = await ResetToHomeAndWaitAsync();
if (!resetSuccess) {
Log("⚠️ 页面重置超时,无法注入请求");
ResponseText(ctx, "{\"error\":\"Reset Timeout\"}", 500, "application/json");
return;
}
}
else
{
Log("延续历史对话 (" + roleCount.ToString() + "条消息)");
}
}
else
{
Log("延续对话 (自动新话题已关闭)");
}
bool isStream = body.Contains("\"stream\": true") || body.Contains("\"stream\":true");
string prompt = ExtractPromptFromJson(body);
if (string.IsNullOrWhiteSpace(prompt)) {
if (!body.Trim().StartsWith("{")) prompt = body;
else { ResponseText(ctx, "{\"error\": \"Parse Error\"}", 400, "application/json"); return; }
}
_currentContext = ctx;
_isCurrentRequestStreaming = isStream;
_networkBuffer.Clear();
_citations.Clear();
_isThinkingOpen = false;
_hasResponseStarted = false;
_lastEventType = "";
_thinkIndex = -1;
_responseIndex = -1;
if (isStream) {
ctx.Response.ContentType = "text/event-stream";
ctx.Response.AppendHeader("Cache-Control", "no-cache");
ctx.Response.AppendHeader("Connection", "keep-alive");
} else { ctx.Response.ContentType = "application/json"; }
this.Invoke(new Action(() => InjectPromptToWebView(prompt)));
} catch (Exception ex) { Log("Error: " + ex.Message); if (_currentContext == ctx) CleanupRequest(); }
} else { ResponseText(ctx, "Not Found", 404); }
}
private string ExtractPromptFromJson(string json)
{
try {
var messagesRegex = new Regex("\"messages\"\\s*:\\s*\\[([^\\]]*(?:\\{[^\\}]*\\}[^\\]]*)*)\\]");
var messagesMatch = messagesRegex.Match(json);
if (messagesMatch.Success) {
var messagesContent = messagesMatch.Groups[1].Value;
var userMatch = Regex.Match(messagesContent, "\"role\"\\s*:\\s*\"user\"[\\s\\S]*?\"content\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
if (userMatch.Success) {
return UnescapeJson(userMatch.Groups[1].Value);
}
}
var roleRegex = new Regex("\"role\"\\s*:\\s*\"(system|user|assistant)\"");
var contentRegex = new Regex("\"content\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
var roles = roleRegex.Matches(json).Cast<Match>().ToList();
var contents = contentRegex.Matches(json).Cast<Match>().ToList();
if (roles.Count > 0 && roles.Count == contents.Count) {
for (int i = 0; i < roles.Count; i++) {
string r = roles[i].Groups[1].Value.ToLower();
string c = UnescapeJson(contents[i].Groups[1].Value);
if (r == "system") return "[System Instructions]:\n" + c + "\n\n[User Message]:\n" + UnescapeJson(contents[contents.Count - 1].Groups[1].Value);
if (r == "user") return c;
}
} else {
var lastMatch = contents.LastOrDefault();
if (lastMatch != null) return UnescapeJson(lastMatch.Groups[1].Value);
}
return null;
} catch { return null; }
}
private void InjectPromptToWebView(string prompt)
{
// V54/V55: 采用内联 IIFE 模式 + React NativeSetter 修复
string encodedPrompt = Uri.EscapeDataString(prompt);
string script = "(function() { " +
"try { " +
" window.chrome.webview.postMessage('[LOG] V55 Start'); " +
" const text = decodeURIComponent(\"" + encodedPrompt + "\"); " +
" window.chrome.webview.postMessage('[LOG] V55 Decoded: ' + text); " +
" let inputs = Array.from(document.querySelectorAll('textarea, input[type=\"text\"], div[contenteditable=\"true\"]')); " +
" if (inputs.length === 0) { window.chrome.webview.postMessage('[LOG] V55 Error: No input'); return; } " +
" let input = inputs[inputs.length - 1]; " +
" window.chrome.webview.postMessage('[LOG] V55 Input Tag: ' + input.tagName); " +
" if (input.tagName === 'DIV') { " +
" input.innerText = text; " +
" } else if (input.tagName === 'TEXTAREA') { " +
" const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; " +
" try { " +
" nativeSetter.call(input, text); " +
" window.chrome.webview.postMessage('[LOG] V55 Set via NativeSetter'); " +
" } catch (e) { " +
" input.value = text; " +
" } " +
" } else { " +
" input.value = text; " +
" } " +
" input.dispatchEvent(new Event('input', { bubbles: true })); " +
" input.focus(); " +
" setTimeout(() => { " +
" window.chrome.webview.postMessage('[LOG] V55 Attempt Enter'); " +
" input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true })); " +
" setTimeout(() => { " +
" let sendBtn = document.querySelector('button[aria-label=\"Send\"]') || document.querySelector('div[aria-label=\"Send\"]') || Array.from(document.querySelectorAll('div[role=\"button\"]')).pop(); " +
" if (sendBtn) { " +
" window.chrome.webview.postMessage('[LOG] V55 Click Button'); " +
" sendBtn.click(); " +
" } else { " +
" input.blur(); " +
" } " +
" }, 500); " +
" }, 500); " +
"} catch (e) { " +
" window.chrome.webview.postMessage('[LOG] V55 Exception: ' + e); " +
"} " +
"})();";
Log("[Debug] 注入 V55 内联脚本");
if (_coreWebView != null) {
try {
_coreWebView.ExecuteScriptAsync(script).ContinueWith(t => {
if (t.IsFaulted) {
Log("[Debug] ❌ 脚本失败: " + t.Exception.InnerException.Message);
try {
if (_currentContext != null) ResponseText(_currentContext, "{\"error\":\"Script Failed\"}", 500, "application/json");
} catch { }
} else {
string result = t.Result;
Log("[Debug] ✅ 脚本执行完成: " + result);
}
});
} catch (Exception ex) {
Log("[Debug] ❌ 启动异常: " + ex.Message);
}
}
else {
Log("[Debug] ❌ WebView2 为空");
}
}
private void OnWebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
try {
string msg = e.TryGetWebMessageAsString();
if (msg.StartsWith("[LOG]")) { Log(msg); return; }
if (_currentContext == null) return;
if (msg.StartsWith("[NETWORK_DONE]")) { FinishRequest(); return; }
if (msg.StartsWith("[NETWORK_DATA]")) { string rawData = msg.Substring(14); ProcessNetworkData(rawData); }
} catch (Exception ex) { Log("MsgError: " + ex.Message); }
}
private void ProcessNetworkData(string rawChunk)
{
_networkBuffer.Append(rawChunk);
string bufferStr = _networkBuffer.ToString();
if (_isDebugMode && rawChunk.Length > 0) {
string debugSnippet = rawChunk.Length > 100 ? rawChunk.Substring(0, 100) + "..." : rawChunk;
Log("[RAW] " + debugSnippet.Replace("\n", "\\n"));
}
int lastNewLine = bufferStr.LastIndexOf('\n');
if (lastNewLine == -1) return;
string processablePart = bufferStr.Substring(0, lastNewLine + 1);
_networkBuffer.Remove(0, lastNewLine + 1);
using (StringReader reader = new StringReader(processablePart))
{
string line;
while ((line = reader.ReadLine()) != null) {
if (string.IsNullOrWhiteSpace(line)) { _lastEventType = ""; continue; }
line = line.Trim();
if (line.StartsWith("event:")) {
_lastEventType = line.Substring(6).Trim();
continue;
}
if (line.StartsWith("data:")) {
if (_lastEventType == "title" || _lastEventType == "update_session" || _lastEventType == "close") continue;
line = line.Substring(5).Trim();
}
else if (line.StartsWith("message")) line = line.Substring(7).Trim();
if (!line.StartsWith("{") && !line.StartsWith("[")) continue;
if (line.Contains("\"type\""))
{
if (line.Contains("THINK"))
{
_thinkIndex = 0;
if (!_isThinkingOpen) { SendDelta("\n\n"); _isThinkingOpen = true; }
var initContent = ExtractValueFromJson(line, "content");
if (!string.IsNullOrEmpty(initContent)) SendDelta(initContent);
}
else if (line.Contains("RESPONSE"))
{
if (_isThinkingOpen) { SendDelta("\n\n"); _isThinkingOpen = false; }
_responseIndex = (_thinkIndex == 0) ? 1 : 0;
_hasResponseStarted = true;
var initContent = ExtractValueFromJson(line, "content");
if (!string.IsNullOrEmpty(initContent)) SendDelta(initContent);
}
continue;
}
bool isProcessed = false;
if (line.Contains("/content\""))
{
var idMatch = Regex.Match(line, "\"p\"\\s*:\\s*\"response/fragments/(\\d+)/content\"");
if (idMatch.Success)
{
isProcessed = true;
int currentIndex = int.Parse(idMatch.Groups[1].Value);
var contentMatch = Regex.Match(line, "\"v\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
if (contentMatch.Success)
{
string content = UnescapeJson(contentMatch.Groups[1].Value);
bool isThink = (currentIndex == _thinkIndex);
bool isResponse = (currentIndex == _responseIndex);
if (_thinkIndex == -1 && _responseIndex == -1 && currentIndex == 0)
{
isThink = true;
}
if (isThink) {
if (_hasResponseStarted) {
continue;
}
if (!_isThinkingOpen) { SendDelta("\n\n"); _isThinkingOpen = true; }
SendDelta(content);
}
else if (isResponse) {
if (_isThinkingOpen) { SendDelta("\n\n"); _isThinkingOpen = false; }
_hasResponseStarted = true;
SendDelta(content);
}
else {
SendDelta(content);
}
}
}
}
if (line.Contains("results\"") && line.Contains("\"v\":"))
{
ProcessCitations(line);
isProcessed = true;
}
if (!isProcessed)
{
string content = ExtractValueFromJson(line, "content");
if (string.IsNullOrEmpty(content)) content = ExtractValueFromJson(line, "v");
if (!string.IsNullOrEmpty(content) && content != "FINISHED" && !line.Contains("/elapsed_secs") && !line.Contains("\"p\":"))
{
if (_isThinkingOpen) {
SendDelta(content);
} else {
SendDelta(content);
}
}
}
}
}
}
private void ProcessCitations(string json)
{
try {
var urlMatches = Regex.Matches(json, "\"url\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
var titleMatches = Regex.Matches(json, "\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
int count = Math.Min(urlMatches.Count, titleMatches.Count);
for (int i = 0; i < count; i++) {
string url = UnescapeJson(urlMatches[i].Groups[1].Value);
string title = UnescapeJson(titleMatches[i].Groups[1].Value);
string citationEntry = "[" + (_citations.Count + 1).ToString() + "] [" + title + "](" + url + ")";
if (!_citations.Contains(citationEntry)) _citations.Add(citationEntry);
}
} catch { }
}
private string ExtractValueFromJson(string json, string key) {
var regex = new Regex("\"" + key + "\":\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
var match = regex.Match(json);
if (match.Success) return UnescapeJson(match.Groups[1].Value);
return null;
}
private string UnescapeJson(string str) {
return str.Replace("\\\"", "\"").Replace("\\\\", "\\").Replace("\\n", "\n").Replace("\\r", "\r").Replace("\\t", "\t");
}
private void SendDelta(string text) {
if (_isCurrentRequestStreaming) {
StringBuilder sb = new StringBuilder();
sb.Append("{\"id\":\"chatcmpl-deepseek\",\"object\":\"chat.completion.chunk\",\"created\":");
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
sb.Append(",\"model\":\"deepseek-chat\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"");
sb.Append(EscapeJson(text));
sb.Append("\"},\"finish_reason\":null}]}");
WriteSseData("data: " + sb.ToString() + "\n\n");
}
Log("[NET] " + text.Replace("\n", "\\n"));
}
private void FinishRequest() {
Log("Stream Finished");
if (_networkBuffer.Length > 0) { ProcessNetworkData("\n"); _networkBuffer.Clear(); }
if (_isThinkingOpen) {
SendDelta("\n\n");
_isThinkingOpen = false;
}
if (_citations.Count > 0) {
StringBuilder sb = new StringBuilder();
sb.Append("\n\n**引用来源:**\n");
foreach (var cite in _citations) sb.Append(cite + "\n");
SendDelta(sb.ToString());
}
if (_isCurrentRequestStreaming) {
StringBuilder sb = new StringBuilder();
sb.Append("{\"id\":\"chatcmpl-deepseek\",\"object\":\"chat.completion.chunk\",\"created\":");
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
sb.Append(",\"model\":\"deepseek-chat\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}");
WriteSseData("data: " + sb.ToString() + "\n\n");
WriteSseData("data: [DONE]\n\n");
} else {
string finalResponse = "{\"id\":\"chatcmpl-deepseek\",\"object\":\"chat.completion\",\"created\":{0},\"model\":\"deepseek-chat\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"{1}\"},\"finish_reason\":\"stop\"}]}";
finalResponse = string.Format(finalResponse, DateTimeOffset.Now.ToUnixTimeSeconds(), EscapeJson(_networkBuffer.ToString()));
ResponseText(_currentContext, finalResponse, 200, "application/json");
}
CleanupRequest();
}
private void WriteSseData(string data) {
try {
byte[] buffer = Encoding.UTF8.GetBytes(data);
_currentContext.Response.OutputStream.Write(buffer, 0, buffer.Length);
_currentContext.Response.OutputStream.Flush();
} catch { CleanupRequest(); }
}
private void CleanupRequest() {
try {
if (_currentContext != null)
{
_currentContext.Response.Close();
}
} catch { }
_currentContext = null;
_networkBuffer.Clear();
}
private void ResponseText(HttpListenerContext ctx, string text, int code = 200, string contentType = "text/plain") {
try {
ctx.Response.StatusCode = code; ctx.Response.ContentType = contentType;
byte[] buffer = Encoding.UTF8.GetBytes(text);
ctx.Response.ContentLength64 = buffer.Length;
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
ctx.Response.Close();
} catch { }
}
private string EscapeJson(string s) {
if (s == null) return "";
// V55: 修复 JSON-in-JSON 崩溃,转义大括号
return s.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("{", "\\u007B")
.Replace("}", "\\u007D")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}
private void Log(string msg) {
if (!_isDebugMode) return;
if (_txtLog != null && !_txtLog.IsDisposed) {
if (_txtLog.InvokeRequired) {
_txtLog.Invoke(new Action(() => Log(msg)));
}
else {
_txtLog.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + msg + "\r\n");
}
}
}
private string GetNetworkInterceptorScript() {
return "window.chrome.webview.postMessage('[LOG] Interceptor Mounting (V55)...');" +
"const originalFetch = window.fetch;" +
"window.fetch = async (...args) => {" +
"let url = '';" +
"try { if (args[0] instanceof Request) url = args[0].url; else url = args[0].toString(); } catch(e) { url = 'unknown'; }" +
"if (url.indexOf('chat') !== -1 && url.indexOf('completion') !== -1) {" +
"window.chrome.webview.postMessage('[LOG] [Fetch] Intercepted: ' + url);" +
"try {" +
"const response = await originalFetch(...args);" +
"const clone = response.clone();" +
"const reader = clone.body.getReader();" +
"const decoder = new TextDecoder();" +
"(async () => {" +
"try {" +
"while (true) {" +
"const { done, value } = await reader.read();" +
"if (done) { window.chrome.webview.postMessage('[NETWORK_DONE]'); break; }" +
"const text = decoder.decode(value, {stream: true});" +
"window.chrome.webview.postMessage('[NETWORK_DATA]' + text);" +
"}" +
"} catch (e) { window.chrome.webview.postMessage('[LOG] Fetch Stream Error: ' + e); }" +
"})();" +
"return response;" +
"} catch (e) { window.chrome.webview.postMessage('[LOG] Fetch Exec Error: ' + e); return originalFetch(...args); }" +
"}" +
"return originalFetch(...args);" +
"};" +
"const OriginalXHR = window.XMLHttpRequest;" +
"window.XMLHttpRequest = function() {" +
"const xhr = new OriginalXHR();" +
"let url = '';" +
"const originalOpen = xhr.open;" +
"xhr.open = function(method, requestUrl) {" +
"url = requestUrl || '';" +
"if (url.indexOf('chat') !== -1 && url.indexOf('completion') !== -1) { window.chrome.webview.postMessage('[LOG] [XHR] Intercepted: ' + url); }" +
"return originalOpen.apply(this, arguments);" +
"};" +
"xhr.addEventListener('progress', function() {" +
"if (url.indexOf('chat') !== -1 && url.indexOf('completion') !== -1) {" +
"try {" +
"let fullText = '';" +
"try { fullText = xhr.responseText; } catch(e) { return; }" +
"if (!fullText) return;" +
"const lastLen = xhr._lastLength || 0;" +
"const newChunk = fullText.substring(lastLen);" +
"if (newChunk.length > 0) {" +
"window.chrome.webview.postMessage('[NETWORK_DATA]' + newChunk);" +
"xhr._lastLength = fullText.length;" +
"}" +
"} catch(e) { window.chrome.webview.postMessage('[LOG] XHR Progress Error: ' + e); }" +
"}" +
"});" +
"xhr.addEventListener('load', function() { if (url.indexOf('chat') !== -1 && url.indexOf('completion') !== -1) { window.chrome.webview.postMessage('[NETWORK_DONE]'); } });" +
"return xhr;" +
"};" +
"window.chrome.webview.postMessage('[LOG] Interceptor Ready');";
}
}
我也是让AI编程的,不过要把官方C#提示词写进去,但是也不简单,至于支持CC的话可能要自行开发了。我没有这方面需求
感谢回复;几经改版,AI判断可能是cc缺少支持,我先给项目提交个issues试试