大佬这个动作有办法支持 CC Switch 这类工具吗

使用问题 · 11 次浏览
lanyu_er 创建于 3天13小时前

我尝试使用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');";
    }
}

洛洛罗 2天9小时前 :

我也是让AI编程的,不过要把官方C#提示词写进去,但是也不简单,至于支持CC的话可能要自行开发了。我没有这方面需求

lanyu_er 回复 洛洛罗 1天23小时前 :

感谢回复;几经改版,AI判断可能是cc缺少支持,我先给项目提交个issues试试

回复内容
暂无回复
回复主贴