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

配置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;
          }
        })

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值