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

vue3+vite+AI大模型实现谷歌插件-web诊断

vue3+vite+AI大模型实现谷歌插件-web诊断

  • 一、前言
  • 二、实现思路
    • 1、功模块构图
    • 2、数据交互图
  • 三、技术栈简介
    • 1、Web端
    • 2、服务端
  • 四、主要功能实现
    • 1、Web端
      • 【1】谷歌插件+vue全局配置文件
      • 【2】加载web诊断工具至当前页面
      • 【3】全局捕获异常错误
    • 2、Server端
      • 【1】websock管理模块
      • 【2】调用大模型API接口

一、前言

    谷歌插件-web诊断工具是基于vue3、vite和AI大模型技术的完美结合,工具致力于捕获页面报错信息,并为您提供高效的解决方案。

二、实现思路

1、功模块构图

功能模块图

2、数据交互图

web(谷歌插件) 服务端(NodeJS) AI大模型(百炼) 错误类型:报错信息 {model: "qwen-coder-plus", messages:错误类型:报错信息} 错误解释+解决方案 错误解释+解决方案(websocket) web(谷歌插件) 服务端(NodeJS) AI大模型(百炼)

三、技术栈简介

1、Web端

名称版本备注
谷歌插件协议“manifest_version”: 3
element-plus^2.9.4
axios^1.8.4
vditor^3.10.9官网地址:https://b3log.org/vditor/

2、服务端

名称版本备注
Nodev22.13.0
express^5.1.0
openai^4.91.1用于大模型进行交互
ws^8.18.1

四、主要功能实现

1、Web端

【1】谷歌插件+vue全局配置文件

  • vite.config.js
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({plugins: [vue(),vueDevTools(),],build: {outDir: 'dist', // 设置输出目录为扩展的 _dist_ 文件夹rollupOptions: {input: 'index.html',output: {entryFileNames: 'assets/[name].js',chunkFileNames: 'assets/[name].js',assetFileNames: 'assets/[name].[ext]'}}},base: './', // 设置为相对路径,以适应扩展环境resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))},},
})
  • manifest.json
{"manifest_version": 3,"name": "web 诊断","version": "1.0","description": "A browser extension to log errors.","permissions": ["tabs","activeTab","scripting","storage","webNavigation"],"action": {"default_popup": "index.html"},"background": {"service_worker": "background.js"},"host_permissions": ["http://*/*", "https://*/*"],"content_scripts": [{"matches": ["http://*/*", "https://*/*"],"js": ["contentScript.js"]}],"web_accessible_resources": [{"resources": ["index.html", "/assets/*"],"matches": ["http://*/*", "https://*/*"]}]
}
  • main.js
import './assets/main.css'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App);
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.mount('#zykApp');

【2】加载web诊断工具至当前页面

实现思路: 通过contentScript文件的特性,获取当前页面的上下文,把web诊断Dom动态插入当前页面的文档流中。
文件名称: contentScript.js (谷歌插件预置文件名)
文件描述: contentScript文件是能够在网页的上下文中运行的JavaScript代码片段,


const getFilePath = (url) => {const urlSplit = url.split('/assets/');return `/assets/${urlSplit[1]}`;
}
// 获取当前页面的 window 和 document 对象
const currentPageWindow = window;
const currentPageDocument = document;// contentScript.js
(function() {function injectVueApp() {const appContainer = document.createElement('div');appContainer.id = 'zyk-app-container';appContainer.style.position = 'fixed'; // 确保容器固定在页面上appContainer.style.top = '48px';appContainer.style.right = '48px';appContainer.style.width = '48px';appContainer.style.height = '48px';appContainer.style.zIndex = '999999';appContainer.style.backgroundColor = 'transparent'; // 可选:设置背景颜色document.body.appendChild(appContainer);// 动态插入 index.html 文件的内容fetch(chrome.runtime.getURL('index.html')).then(response => response.text()).then(html => {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');// 提取 head 内容const headContent = doc.head.innerHTML;const styleTags = doc.querySelectorAll('style');const linkTags = doc.querySelectorAll('link[rel="stylesheet"]');// 提取 body 内容const bodyContent = doc.querySelector('#zykApp').outerHTML;// 插入 head 内容if (styleTags.length > 0) {for (let i = 0; i < styleTags.length; i++) {const styleTag = document.createElement('style');styleTag.innerHTML = styleTags[i].innerHTML;document.head.appendChild(styleTag);}}if (linkTags.length > 0) {for (let i = 0; i < linkTags.length; i++) {const linkTag = document.createElement('link');linkTag.rel = 'stylesheet';linkTag.href = chrome.runtime.getURL(getFilePath(linkTags[i].href));document.head.appendChild(linkTag);}}// 插入 body 内容appContainer.innerHTML = bodyContent;// 插入 script 标签const scriptTags = doc.querySelectorAll('script[type="module"]');scriptTags.forEach(script => {const newScript = document.createElement('script');newScript.type = 'module';newScript.src = chrome.runtime.getURL(getFilePath(script.src));document.head.appendChild(newScript);});}).catch(error => {console.error('Failed to load Vue app HTML:', error);});}// 确保 DOM 已经加载完成if (document.readyState === 'complete' || document.readyState === 'interactive') {injectVueApp();} else {window.addEventListener('load', injectVueApp);}
})();

【3】全局捕获异常错误

  • JS错误
const captureErrors = () => {window.addEventListener('error', event => {console.log('error',event)// errorType: SyntaxError(语法错误)/ReferenceError(引用错误)/TypeError(类型错误)//            RangeError(范围错误)/URIError(URI 错误)/EvalError(Eval 错误)//            AggregateError(聚合错误)errors.value.push({type: 'JavaScript Error',message: `${event.message}`,stack: `文件路径:${event.filename} [${event.lineno}:${event.colno}]`,suggestion: '', //todo 更加不同的错误类型给出建议time: new Date().toLocaleString()});})window.addEventListener('unhandledrejection', event => {console.log('unhandledrejection',event)errors.value.push({type: 'Promise Rejection',message: event.reason.message,stack: event.reason.stack,suggestion: '检查 Promise 被拒绝(rejected)后没有被 .catch() 或 try...catch 处理',time: new Date().toLocaleString()});});
}
  • HTTP错误
const watchHttp = () => {// 拦截 XMLHttpRequestconst originalXhrOpen = XMLHttpRequest.prototype.open;const originalXhrSend = XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open = function (method, url) {console.log('open  ZYK', method, url)this._url = url; // 记录请求 URLreturn originalXhrOpen.apply(this, arguments);};XMLHttpRequest.prototype.send = function () {this.addEventListener("load", () => {if (this.status !== 200 && this.status >= 400) {httpFailedList.value.push({url: this._url,status: this.status,time: new Date().toLocaleString(),response: this.responseText,})}});this.addEventListener("error", () => {// 网络中断、域名无法解析、超时。httpFailedList.value.push({url: this._url,status: this.status,time: new Date().toLocaleString(),response: this.responseText,})});return originalXhrSend.apply(this, arguments);};//todo 拦截 fetchconst originalFetch = window.fetch;window.fetch = async function (...args) {const [url, config] = args;try {const response = await originalFetch.apply(this, args);if (!response.ok) {console.error(`Fetch 请求失败: ${url}`, response.statusText);} else {console.log(`Fetch 请求成功: ${url}`, response);}return response;} catch (error) {console.error(`Fetch 请求错误: ${url}`, error);throw error;}};
}

2、Server端

【1】websock管理模块

const WebSocket = require('ws');
// 创建 WebSocket 服务器
const wss = new WebSocket.Server({ server });// 监听 WebSocket 连接
wss.on('connection', (ws) => {console.log('客户端已连接');// todo 测试场景屏蔽// ws.send(formatSendMessage({ type: 'ping', message: 'ping' }))// 接收客户端消息ws.on('message', (message) => {const receiveMessage = formatReceiveMessage(message);if (receiveMessage.type === 'pong') {setTimeout(() => {ws.send(formatSendMessage({ type: 'ping', message: 'ping' }));}, 5 * 1000)}  else {console.log(`收到客户端消息(未匹配type类型): ${message}`)}});// 监听客户端断开连接ws.on('close', () => {ws.close();console.log('客户端已断开连接');});
});

【2】调用大模型API接口

const OpenAI = require('openai');
const express = require('express');
const WebSocket = require('ws');
const http = require('http'); // 用于创建 HTTP 服务器
const app = express();
// 创建 HTTP 服务器并挂载 Express 应用
const server = http.createServer(app);
app.use(express.json());
app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*'); // 允许所有域名访问res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); // 允许的 HTTP 方法res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头next();
});
//触发大模型
app.post('/zyk/api/ai', (req, res) => {// 获取请求体中的数据const requestData = req.body;const question = `${requestData.type}:${requestData.message}`;const isExistQuestion = aiContent.find(item => item.question === question);if (isExistQuestion) {res.status(200).json({useCache: true,data: isExistQuestion.answer});} else {res.status(200).json({useCache: false});createAi(question);}
});
const openai = new OpenAI({// todo 使用环境变量 若没有配置环境变量,apiKey: 'API-KEY', // 如何获取API Key:https://help.aliyun.com/zh/model-studio/developer-reference/get-api-keybaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",}
);async function createAi(message) {const completion = await openai.chat.completions.create({model: "qwen-coder-plus",  // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/modelsmessages: [{ role: "system", content: "作为一个高级前端专家,你需要根据报错信息给出准确的解释,并给出修复建议" },{ role: "user", content: message }],max_tokens: 200,});aiContent.push({question: message, answer: completion.choices[0].message.content})// ws 通知前端wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(formatSendMessage({ type: 'message', message: aiContent }))}})
http://www.xdnf.cn/news/4291.html

相关文章:

  • 高频PCB设计如何选择PCB层数?
  • 视觉爬虫开发:通过Puppeteer截图+CV定位动态元素坐标
  • 线上部署的项目Redis突然宕机了怎么办
  • 解决 Exception in thread “main“ java.lang.NoClassDefFoundError
  • CPU:为什么Ryzen 7000系列处理器PCIe通道总数是28,而可用的通道数是24?
  • 【coze】工作流(B站视频总结改写)
  • 推荐两本集成电路制作书籍
  • 14.Spring Boot 3.1.5 集成 Spring Security 进行访问控制
  • SQL Server执行安装python环境
  • GIS中常见的影像数据格式和类型
  • 【SpringBoot教程】SpringBoot自定义注解与AOP实现切面日志
  • Spring Boot Starter简介-笔记
  • UE5 C++项目实现单例
  • 基于STM32、HAL库的TTP224C-BSBN 触摸屏控制器驱动程序设计
  • ultralytics框架进行RT-DETR目标检测训练
  • 人工智能 计算智能模糊逻辑讲解
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十一讲)
  • clickhouse - 重新建表覆盖旧表-解决分区时间错误问题-197001
  • GEC6818蜂鸣器驱动开发
  • K8S - Helm 入门与实战 - 应用部署与依赖治理
  • BERT 微调
  • Linux系统之shell脚本基础:条件测试、正整数字符串比较与if、case语句
  • 第四节:进程控制
  • 8086汇编:寄存器
  • 匿名函数对编译错误的影响
  • JVM——垃圾回收
  • 开发规范 - 空指针异常等低级问题注意点
  • 10B扩散文生图模型F-Lite技术报告速读
  • C++ 享元模式与共享工厂模式详解
  • Java学习手册:分库分表策略