当前位置: 首页 > article >正文

谷歌浏览器插件开发避免跨域以及流式数据输出

配置manifest.json

"host_permissions": ["<all_urls>"],
"web_accessible_resources": [{
      "resources": ["background.js"],
      "matches": ["<all_urls>"]
}],
"background": {
  "service_worker": "background.js"
},
    "content_scripts": [
      {
        "matches": ["<all_urls>"],
        "js": ["background.js"],
      }
    ]

在background.js 当中写

// 监听connect事件
let abortController = null;

chrome.runtime.onConnect.addListener(function (port) {
  console.log("Connected:", port);

  port.onMessage.addListener(async function (message) {
    if (message.action === 'stream') {
      // 创建一个新的 AbortController 实例
      abortController = new AbortController();
      const { signal } = abortController;

      try {
        const response = await fetch(message.url, {
          method: message.method,
          body: message.body,
          headers: message.headers ? message.headers : { 'Content-Type': 'application/json' },
          signal  // 将信号传递给 fetch 请求
        });

        if (!response.ok) {
          port.postMessage({ type: 'error', msg: '服务器错误,请检查设置页面是否配置大模型地址' });
          throw new Error('Network response was not ok');
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');

        let partialMessage = '';

        while (true) {
          const { done, value } = await reader.read();

          if (done || signal.aborted) {
            if (signal.aborted) {
              console.log('Request was aborted by user.');
            } else {
              port.postMessage({ type: 'DONE', msg: '完成' });
            }
            break;
          }

          const chunk = decoder.decode(value, { stream: true });
          partialMessage += chunk;

          try {
            const lines = partialMessage.split('\n').filter(line => line.trim() !== '');
            if (lines.length > 0 && !lines[0].startsWith('data:')) {
              partialMessage = lines.pop() || '';
            }

            for (let i = 0; i < lines.length; i++) {
              const line = lines[i];
              if (line.startsWith('data:')) {
                const jsonDataStr = line.slice(5).trim();
                if (jsonDataStr === '[DONE]') {
                  console.log('Stream ended.');
                  break;
                }
                const jsonData = JSON.parse(jsonDataStr);
                if (jsonData.choices && jsonData.choices[0].delta.content) {
                  const content = jsonData.choices[0].delta.content;
                  port.postMessage({ type: 'success', msg: content });
                }
              } else {
                const jsonData = JSON.parse(line);
                if (jsonData.message && jsonData.message.content) {
                  const content = jsonData.message.content;
                  port.postMessage({ type: 'success', msg: content });
                }
                if (jsonData.done === true) {
                  console.log('Stream ended.');
                  break;
                }
              }
            }
            if (lines.length > 0 && lines[0].startsWith('data:')) {
              partialMessage = ''; 
            }
          } catch (e) {
            console.error('Error parsing data:', e);
            continue;
          }
        }
      } catch (error) {
        if (!signal.aborted) {
          port.postMessage({ type: 'error', msg: error.message });
        }
      }
    } else if (message.action === 'abort') {
      // 用户请求中断
      if (abortController) {
        abortController.abort();
      }
    }
  });
});

接口封装

// 监听connect事件
let abortController = null;

chrome.runtime.onConnect.addListener(function (port) {
  console.log("Connected:", port);

  port.onMessage.addListener(async function (message) {
    if (message.action === 'stream') {
      // 创建一个新的 AbortController 实例
      abortController = new AbortController();
      const { signal } = abortController;

      try {
        const response = await fetch(message.url, {
          method: message.method,
          body: message.body,
          headers: message.headers ? message.headers : { 'Content-Type': 'application/json' },
          signal  // 将信号传递给 fetch 请求
        });

        if (!response.ok) {
          port.postMessage({ type: 'error', msg: '服务器错误,请检查设置页面是否配置大模型地址' });
          throw new Error('Network response was not ok');
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');

        let partialMessage = '';

        while (true) {
          const { done, value } = await reader.read();

          if (done || signal.aborted) {
            if (signal.aborted) {
              console.log('Request was aborted by user.');
            } else {
              port.postMessage({ type: 'DONE', msg: '完成' });
            }
            break;
          }

          const chunk = decoder.decode(value, { stream: true });
          partialMessage += chunk;

          try {
            const lines = partialMessage.split('\n').filter(line => line.trim() !== '');
            if (lines.length > 0 && !lines[0].startsWith('data:')) {
              partialMessage = lines.pop() || '';
            }

            for (let i = 0; i < lines.length; i++) {
              const line = lines[i];
              if (line.startsWith('data:')) {
                const jsonDataStr = line.slice(5).trim();
                if (jsonDataStr === '[DONE]') {
                  console.log('Stream ended.');
                  break;
                }
                const jsonData = JSON.parse(jsonDataStr);
                if (jsonData.choices && jsonData.choices[0].delta.content) {
                  const content = jsonData.choices[0].delta.content;
                  port.postMessage({ type: 'success', msg: content });
                }
              } else {
                const jsonData = JSON.parse(line);
                if (jsonData.message && jsonData.message.content) {
                  const content = jsonData.message.content;
                  port.postMessage({ type: 'success', msg: content });
                }
                if (jsonData.done === true) {
                  console.log('Stream ended.');
                  break;
                }
              }
            }
            if (lines.length > 0 && lines[0].startsWith('data:')) {
              partialMessage = ''; 
            }
          } catch (e) {
            console.error('Error parsing data:', e);
            continue;
          }
        }
      } catch (error) {
        if (!signal.aborted) {
          port.postMessage({ type: 'error', msg: error.message });
        }
      }
    } else if (message.action === 'abort') {
      // 用户请求中断
      if (abortController) {
        abortController.abort();
      }
    }
  });
});

使用

chat(message, signal, {
          onMessage: (data) => {
            machineMessage.content = data;
            this.$nextTick(() => {
              this.$refs.chatList.scrollBottom();
            })
          },
          onError: (error) => {
            this.requesting = false;
            machineMessage.content = error;
            this.$refs.messageInput.stopRequest();
          },
          onDone: (data) => {
            this.$refs.messageInput.stopRequest();
            this.requesting = false;
          }
        })

参考:谷歌浏览器插件开发跨域问题(完美解决)_谷歌跨域插件-CSDN博客


http://www.kler.cn/a/573372.html

相关文章:

  • Oracle常用分析诊断工具(9)——AWR
  • OpenAI 最后一代非推理模型:OpenAI 发布 GPT-4.5预览版
  • 多线程的使用、同步和异步、synchronized、线程安全的单例模式、死锁、解决死锁
  • Windows系统编程(八)线程同步
  • 系统架构设计师—软件工程基础篇—系统分析与设计
  • FastGPT 源码:utils.ts 中的 RRF 实现
  • C++学习之路,从0到精通的征途:入门基础
  • yum修改阿里云
  • C#—csv文件格式操作实例【在winform表格中操作csv】
  • 【文献阅读】Efficient Prompting Methods for Large Language Models: A Survey
  • 设计模式(7)——SOLID原则之接口隔离原则
  • Kotlin中的数字
  • React - Hooks - useRef
  • 物联网感应层设备的通信协议及数据上传路径详解
  • html+js 轮播图
  • [项目]基于FreeRTOS的STM32四轴飞行器: 二.项目搭建及移植FreeRTOS
  • 在 Apache Tomcat 中,部署和删除项目
  • 物联网感知层常用感应设备
  • Milvus安装linux操作步骤
  • 初识Qt · Qt的基本认识和基本项目代码解释