Chrome Extension + 本地服务:浏览器页面上下文采集与远程执行技术设计

一、背景与需求

在日常开发与自动化测试中,经常需要获取浏览器当前页面的完整 DOM 结构、选中文本、交互元素等上下文信息,或向页面注入 JavaScript 执行特定操作。现有方案(书签脚本、手动 DevTools、独立爬虫)存在以下痛点:

  • 书签脚本:无法异步等待结果,难以处理 Promise 和序列化复杂返回值。
  • 手动 DevTools:不可编程,无法集成到自动化流程。
  • 独立爬虫:无法获取动态渲染后的真实 DOM,也无法与用户当前登录态交互。

本文描述了一个 Chrome Extension + 本地 Python 服务 的完整技术方案,支持:

  1. 页面内嵌聊天 UI,用户提问时自动发送完整页面上下文到本地服务。
  2. 本地程序通过 HTTP 接口主动触发扩展采集当前活动标签页。
  3. 本地程序通过 HTTP 接口在活动标签页执行任意 JavaScript 代码。

二、系统架构

2.1 整体设计

系统由两个子系统组成:

子系统 技术栈 职责
Chrome Extension Manifest V3, Chrome APIs 注入 UI、采集上下文、执行 JavaScript
本地服务 Python 3.11+, aiohttp HTTP 路由、WebSocket 通道、命令调度

通信链路包含三种模式:

  1. 聊天模式:Content Script → Service Worker(runtime message)→ HTTP POST /chat → 响应返回。
  2. 采集模式:本地客户端 → HTTP POST /capture → Service Worker(WebSocket message)→ Content Script(runtime message)→ 上下文返回。
  3. 执行模式:本地客户端 → HTTP POST /execute → Service Worker(WebSocket message)→ CDP Runtime.evaluate → 结果返回。

2.2 架构图

#mermaid-svg-FZjo8XoIQfiFAVkp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FZjo8XoIQfiFAVkp .error-icon{fill:#552222;}#mermaid-svg-FZjo8XoIQfiFAVkp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FZjo8XoIQfiFAVkp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FZjo8XoIQfiFAVkp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FZjo8XoIQfiFAVkp .marker.cross{stroke:#333333;}#mermaid-svg-FZjo8XoIQfiFAVkp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FZjo8XoIQfiFAVkp p{margin:0;}#mermaid-svg-FZjo8XoIQfiFAVkp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster-label text{fill:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster-label span{color:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster-label span p{background-color:transparent;}#mermaid-svg-FZjo8XoIQfiFAVkp .label text,#mermaid-svg-FZjo8XoIQfiFAVkp span{fill:#333;color:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp .node rect,#mermaid-svg-FZjo8XoIQfiFAVkp .node circle,#mermaid-svg-FZjo8XoIQfiFAVkp .node ellipse,#mermaid-svg-FZjo8XoIQfiFAVkp .node polygon,#mermaid-svg-FZjo8XoIQfiFAVkp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FZjo8XoIQfiFAVkp .rough-node .label text,#mermaid-svg-FZjo8XoIQfiFAVkp .node .label text,#mermaid-svg-FZjo8XoIQfiFAVkp .image-shape .label,#mermaid-svg-FZjo8XoIQfiFAVkp .icon-shape .label{text-anchor:middle;}#mermaid-svg-FZjo8XoIQfiFAVkp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FZjo8XoIQfiFAVkp .rough-node .label,#mermaid-svg-FZjo8XoIQfiFAVkp .node .label,#mermaid-svg-FZjo8XoIQfiFAVkp .image-shape .label,#mermaid-svg-FZjo8XoIQfiFAVkp .icon-shape .label{text-align:center;}#mermaid-svg-FZjo8XoIQfiFAVkp .node.clickable{cursor:pointer;}#mermaid-svg-FZjo8XoIQfiFAVkp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FZjo8XoIQfiFAVkp .arrowheadPath{fill:#333333;}#mermaid-svg-FZjo8XoIQfiFAVkp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FZjo8XoIQfiFAVkp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FZjo8XoIQfiFAVkp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FZjo8XoIQfiFAVkp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FZjo8XoIQfiFAVkp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FZjo8XoIQfiFAVkp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster text{fill:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp .cluster span{color:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FZjo8XoIQfiFAVkp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FZjo8XoIQfiFAVkp rect.text{fill:none;stroke-width:0;}#mermaid-svg-FZjo8XoIQfiFAVkp .icon-shape,#mermaid-svg-FZjo8XoIQfiFAVkp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FZjo8XoIQfiFAVkp .icon-shape p,#mermaid-svg-FZjo8XoIQfiFAVkp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FZjo8XoIQfiFAVkp .icon-shape .label rect,#mermaid-svg-FZjo8XoIQfiFAVkp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FZjo8XoIQfiFAVkp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FZjo8XoIQfiFAVkp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FZjo8XoIQfiFAVkp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 页面提问
runtime message
POST /chat
reply
reply
POST /capture
capture_page / WebSocket
CAPTURE_PAGE_CONTEXT
context
capture_result
POST /execute
execute_script / WebSocket
CDP Runtime.evaluate
result
execute_result
本地用户/程序
Content Script

Shadow DOM Chat
Extension Service Worker
aiohttp Server

127.0.0.1:8090
当前活动页面

2.3 为什么选择 WebSocket 作为命令通道

最初考虑过两种方案:

方案 优点 缺点
定时轮询(扩展每隔 N 秒请求一次) 实现简单 延迟不可控、资源浪费、无法实时响应
WebSocket 长连接 双向实时、低延迟、按需通信 Service Worker 保活需要额外处理

最终选择 WebSocket 。Chrome Service Worker 在 30 秒无活动后会进入休眠,通过每 20 秒发送 ping 保持连接活跃。


三、Chrome Extension 设计

3.1 Manifest V3 配置

使用 Manifest V3,声明 debuggertabs 两个权限:

json 复制代码
{
  "manifest_version": 3,
  "minimum_chrome_version": "116",
  "permissions": ["debugger", "tabs"],
  "host_permissions": [
    "http://127.0.0.1:8090/*",
    "http://localhost:8090/*"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ]
}

权限设计要点

  • tabs:用于查询当前聚焦窗口的活动标签页,判断是否为 HTTP/HTTPS 页面。
  • debugger/execute 功能的核心,通过 Chrome DevTools Protocol 在页面主世界执行代码。
  • host_permissions:限制扩展后台只能访问本机 8090 端口,不向任意 URL 开放 fetch。

3.2 Content Script:Shadow DOM 聊天 UI

Content Script 在页面加载完成后注入,使用 closed Shadow DOM 隔离样式:

复制代码
document.createElement("div") → attachShadow({ mode: "closed" })

这种模式下,页面自身的 JavaScript 无法访问组件内部的 DOM 节点和样式,避免了与页面样式冲突。

组件内部包含:

  • 启动按钮:固定在右下角,点击展开聊天面板。
  • 聊天面板:网格布局,包含标题栏、消息列表和输入区。
  • 消息模型userassistant/bubble 两种样式,支持错误状态。

3.3 页面上下文采集

采集函数在用户点击发送或 Service Worker 请求时执行,返回以下结构:

javascript 复制代码
{
  url: location.href,
  title: document.title,
  language: document.documentElement.lang || navigator.language,
  description: getMeta("description") || getMeta("og:description"),
  selectedText: window.getSelection().toString().slice(0, 10000),
  headings: Array.from(document.querySelectorAll("h1, h2, h3")).slice(0, 60),
  interactiveElements: collectInteractiveElements(),
  visibleText: document.body.innerText.replace(/\s+/g, " ").trim().slice(0, 30000),
  html: document.doctype 
    ? `${new XMLSerializer().serializeToString(document.doctype)}\n${document.documentElement.outerHTML}`
    : document.documentElement.outerHTML
}

设计考量

  • interactiveElements 采集 <a><button><input>(排除 password)、<select><textarea>,限制前 80 个元素。
  • visibleText 规范化空白并限制 30,000 字符,避免超大页面导致请求体过大。
  • html 不截断,包含 DOCTYPE 声明,便于后端解析。
  • 密码输入框在 interactiveElements 中排除,但完整 HTML 仍可能包含隐藏敏感信息。

3.4 Service Worker:WebSocket 与命令调度

Service Worker 作为扩展的中枢,承担以下职责:

  1. WebSocket 连接管理

    • 扩展加载后立即连接 ws://127.0.0.1:8090/ws
    • 每 20 秒发送 {"type": "ping"} 保活。
    • 断线后按指数退避重连(1s → 2s → 4s → 8s → 10s 上限)。
  2. HTTP 请求转发

    • 监听 Content Script 的 PAGE_CONTEXT_CHAT runtime message,转发到 /chat
    • 支持 30 秒超时和 AbortController 取消。
  3. WebSocket 命令调度

    • 收到 capture_page 命令 → 查询活动标签页 → 向 Content Script 发送 CAPTURE_PAGE_CONTEXT → 回传结果。
    • 收到 execute_script 命令 → 查询活动标签页 → 附加 CDP 调试器 → 执行 Runtime.evaluate → 回传结果。

3.5 JavaScript 执行:CDP Runtime.evaluate

核心实现代码:

javascript 复制代码
async function executeInActivePage(requestId, code) {
  const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
  if (!tab?.id || !/^https?:\/\//.test(tab.url || "")) {
    throw new Error("当前活动页不是可执行的 HTTP/HTTPS 页面");
  }

  const debuggee = { tabId: tab.id };
  await chrome.debugger.attach(debuggee, "1.3");
  const response = await chrome.debugger.sendCommand(debuggee, "Runtime.evaluate", {
    expression: code,
    awaitPromise: true,      // 支持异步代码
    returnByValue: true,     // 尽可能序列化为 JSON
    userGesture: true,       // 模拟用户操作
    timeout: 10000           // 浏览器执行超时
  });
  // ... 处理结果和异常
  await chrome.debugger.detach(debuggee);
}

技术要点

  • awaitPromise: true:当代码返回 Promise 时,CDP 会等待 Promise resolve 后再返回结果。
  • returnByValue: true:CDP 会尽量将返回值序列化为 JSON。函数、DOM 节点、循环引用对象无法序列化,此时只能通过 typesubtypedescription 描述。
  • 执行结束后必须 detach 调试器,否则 Chrome DevTools 无法再次附加到该标签页。

四、本地服务设计

4.1 技术选型

场景 备选方案 选择 理由
HTTP 框架 Flask, FastAPI, aiohttp aiohttp 原生支持 WebSocket,单个进程处理所有路由
WebSocket 单独 ws 库 + HTTP 服务 aiohttp 内置 统一端口、共享事件循环

4.2 路由表

方法 路径 处理函数 功能
GET /health health 健康检查
POST /chat chat 聊天消息处理
POST /capture capture 触发页面采集
POST /execute execute_script 执行 JavaScript
GET /ws extension_socket 扩展 WebSocket 通道

4.3 CaptureBroker:请求关联器

CaptureBroker 是服务的核心组件,负责将 HTTP 请求与 WebSocket 回包关联:

python 复制代码
class CaptureBroker:
    def __init__(self):
        self.connections = []       # 存活 WebSocket 连接
        self.pending = {}           # {requestId: (Future, socket)}

    def register(self, socket): pass
    def unregister(self, socket): pass
    async def _request(self, command_type, command, timeout): pass
    def resolve(self, message): pass

请求关联流程

  1. HTTP handler 调用 broker._request(...)
  2. 生成 uuid4().hex 作为 requestId
  3. 创建 asyncio.Future,存入 pending 字典。
  4. 通过 WebSocket 发送命令(包含 requestId)。
  5. 等待 Future 完成(最多 timeout 秒)。
  6. WebSocket 收到 capture_result / execute_result 时,resolve() 设置对应 Future 的结果。
  7. HTTP handler 从 Future 获取结果并返回。

优势

  • 支持并发请求:不同 requestId 对应不同 Future,互不阻塞。
  • 超时安全:asyncio.wait_for 在超时后自动取消等待。
  • 断连清理:扩展断开 WebSocket 时,unregister() 会 set_exception 所有关联的 Future

4.4 中间件:来源校验

python 复制代码
@web.middleware
async def origin_guard(request, handler):
    origin = request.headers.get("Origin")
    if origin and not origin.startswith("chrome-extension://"):
        return json_error(403, "origin_forbidden", ...)
    # ...

安全逻辑

  • Origin 的请求只允许 chrome-extension:// 来源,普通网页的 fetch 无法调用接口。
  • 本地命令行调用(无 Origin)不受限制------因为服务只监听 127.0.0.1,已经隔离了外部网络。

4.5 占位聊天实现

当前 build_placeholder_reply() 只返回一条确认消息,包含收到的页面标题、文本长度和 HTML 长度:

python 复制代码
def build_placeholder_reply(message, context):
    return (
        f"本地服务已收到你的问题:{message}\n\n"
        f"当前页面:{title}\n"
        f"已接收:{context_source},完整 HTML {len(html)} 字符\n\n"
        "这是占位响应。请在 server.py 的 build_placeholder_reply() 中接入实际模型或业务逻辑。"
    )

接入 LLM 服务时,只需替换此函数,接口格式和请求/响应结构无需改动。


五、协议设计

5.1 HTTP 接口

所有接口统一使用 JSON 请求体和响应体(UTF-8 编码)。

成功响应顶层结构

json 复制代码
{
  "requestId": "uuid",
  "reply": "..."         // 仅 /chat
  "context": {...}       // 仅 /capture
  "result": {...}        // 仅 /execute
}

错误响应顶层结构

json 复制代码
{
  "error": {
    "code": "error_code",
    "message": "错误说明"
  }
}

5.2 WebSocket 消息协议

方向 type 说明
服务端 → 扩展 ready 连接建立成功
扩展 → 服务端 hello 扩展名称和版本
扩展 → 服务端 ping 保活心跳
服务端 → 扩展 pong 心跳响应
服务端 → 扩展 capture_page 请求采集上下文
扩展 → 服务端 capture_result 返回上下文或错误
服务端 → 扩展 execute_script 请求执行 JavaScript
扩展 → 服务端 execute_result 返回执行结果

capture_pageexecute_script 消息包含 requestId 字段,回包时必须携带相同的 requestId 用于关联。

5.3 数据大小限制

限制项 上限 位置
HTTP 请求体 50 MB aiohttp client_max_size
WebSocket 消息 50 MB max_msg_size
代码段(UTF-8) 256 KB 服务端 len(code.encode("utf-8")) 校验
可见文本 30,000 字符 Content Script 采集时截断
选中文本 10,000 字符 Content Script 采集时截断

六、关键实现细节

6.1 WebSocket 保活

Chrome Service Worker 在约 30 秒无活动后进入休眠。发送 ping 会触发事件监听,保持 Service Worker 存活:

javascript 复制代码
// background.js
keepaliveTimer = setInterval(() => sendSocketMessage({ type: "ping" }), 20000);

服务端同时设置了 25 秒的 heartbeat 参数,无消息时会自动关闭连接。

6.2 重连退避

javascript 复制代码
let reconnectDelay = 1000;  // 第一次重连延迟 1 秒

function scheduleReconnect() {
  reconnectTimer = setTimeout(() => {
    reconnectTimer = null;
    connectSocket();
  }, reconnectDelay);
  reconnectDelay = Math.min(reconnectDelay * 2, 10000);  // 上限 10 秒
}

6.3 调试器附加与分离

每次执行 JavaScript 时,扩展会:

  1. 查询当前活动标签页。
  2. 调用 chrome.debugger.attach(debuggee, "1.3")
  3. 执行 Runtime.evaluate
  4. finally 块中调用 chrome.debugger.detach(debuggee)

如果 Chrome DevTools 已经附加到同一标签页,attach 会失败,扩展回传 execution_failed 错误。

6.4 DOCTYPE 序列化

document.documentElement.outerHTML 不包含 <!DOCTYPE ...>,需要单独获取:

javascript 复制代码
const doctype = document.doctype 
  ? `${new XMLSerializer().serializeToString(document.doctype)}\n` 
  : "";

七、安全设计

7.1 网络层

  • 服务固定监听 127.0.0.1(回环地址),不对外部网络暴露。
  • origin_guard 中间件阻止带普通网页 Origin 的跨域请求。

7.2 权限层

  • /execute 默认关闭,启动时需显式传入 --allow-js-execution
  • Chrome Extension 要求用户手动接受 debugger 权限,Chrome 会在执行时显示调试器附加提示。
  • 执行目标限定为 HTTP/HTTPS 页面,chrome:// 等受保护页面无法执行。

7.3 数据层

  • 页面上下文不持久化存储(但占位实现在服务端控制台 print 了上下文信息)。
  • 完整 HTML 不截断,发送前应评估页面是否包含密码、令牌或敏感数据。

7.4 当前限制

  • 无访问令牌鉴权:同一台机器的任何进程都可以调用暴露在 127.0.0.1:8090 的接口。
  • 无审计日志:未记录谁调用了哪些接口。
  • 无代码执行白名单:/execute 接受任意 JavaScript 代码。

八、测试策略

8.1 单元测试(aiohttp TestClient)

使用 aiohttp 的 TestClientTestServer 模拟 HTTP 请求和 WebSocket 连接,验证服务端的协议正确性:

python 复制代码
async def test_capture_round_trip(self):
    socket = await client.ws_connect("/ws", origin="chrome-extension://test")
    # 模拟扩展回包
    await socket.send_json({...})
    response = await client.post("/capture")
    # 断言响应结构和字段

测试覆盖:

  • /capture 完整往返。
  • 普通网页 Origin 拒绝(403)。
  • 扩展未连接时返回 503。
  • /execute 默认关闭(403)。
  • 启用后 /execute 完整往返。

8.2 集成测试

独立的 Python 脚本,在真实扩展和服务运行时调用接口:

  • test_capture_api.py:校验 requestIdcontext 和 HTML 完整性。
  • test_execute_api.py:在活动页执行 setTimeout + alert,校验 result.typeresult.value

8.3 测试与生产的关系

组件 测试 生产
HTTP 路由 TestClient aiohttp.web.run_app
WebSocket TestClient ws_connect Chrome Extension 真实连接
页面上下文 模拟回包 Content Script 实时采集
CDP 执行 模拟回包 Chrome debugger API

九、开发与部署

9.1 本地开发

powershell 复制代码
# 安装依赖
python -m pip install -r .\server\requirements.txt

# 启动普通模式
python .\server\server.py

# 启动执行模式
python .\server\server.py --allow-js-execution

Chrome 加载开发模式扩展:chrome://extensions/ → 启用开发者模式 → 加载已解压的扩展程序。

9.2 版本兼容

依赖 最低版本 说明
Chrome 116 WebSocket Service Worker
Python 3.11 AppKey 泛型
aiohttp 3.10 WebSocketResponse.heartbeat

9.3 已知缺陷

  1. 同一个标签页不能同时被 DevTools 和扩展调试器附加 :如果用户已经打开 Chrome DevTools,/execute 会失败。
  2. Service Worker 生命周期:Chrome 可能因内存压力终止 Service Worker,WebSocket 断开后依赖重连逻辑恢复。
  3. CDP 序列化限制returnByValue: true 无法序列化 FunctionSymbol、DOM 节点和循环引用对象。
  4. 多窗口行为tabs.query({ active: true, lastFocusedWindow: true }) 取最后获得焦点的窗口,如果多个 Chrome 窗口存在,行为取决于用户最近点击的窗口。

十、总结

本文设计了一个 Chrome Extension + aiohttp 服务 的技术方案,实现了页面上下文采集和远程 JavaScript 执行两套核心能力。

设计亮点

  1. 消息关联模式 :通过 requestId + asyncio.Future + WebSocket 的异步关联方案,在单个线程内高效处理并发请求。
  2. 安全分层:回环地址 + Origin 校验 + 启动开关 + Chrome 权限体系,形成多层防护。
  3. 渐进式扩展:聊天、采集、执行三个功能按权限和风险递减设计,用户可以根据需要选择启用范围。

适用场景

  • 自动化测试中的页面状态捕获。
  • 本地 LLM 服务获取浏览器上下文。
  • 开发工具中需要对当前浏览页面进行编程控制。

不适用场景

  • 生产环境的多用户服务。
  • 需要长期稳定运行的浏览器自动化。
  • 对 Chrome Web Store 发布有要求的场景(因使用了 debugger 权限)。

https://github.com/wqs-base/Page-Context-Chat

完整代码仓库结构:

text 复制代码
chrome-dom-chat/
├── README.md
├── TECHNICAL_DESIGN.md
├── .gitignore
├── extension/
│   ├── manifest.json
│   ├── background.js
│   └── content.js
└── server/
    ├── requirements.txt
    ├── server.py
    ├── test_server.py
    ├── test_capture_api.py
    └── test_execute_api.py
相关推荐
CodexDave1 小时前
Python 自动化接单实战(九):Windows 免环境交付如何打包与诊断
windows·python·自动化·python自动化·pyinstaller·软件交付·windows打包
CTA量化套保1 小时前
2026年量化入门路线,概念规则和简单实现逐步走
人工智能·python
米码收割机1 小时前
【Python】Python Django+Vue3校园自习室预约管理系统(源码+文档+PPT)【独一无二】
开发语言·python·django
AstartesEternal1 小时前
python第二次作业(列表,字典)
开发语言·python
拉特莉的祈祷机1 小时前
绿色测试日志为什么可能已经失效:用 AET 绑定命令与当前代码
python
三金121381 小时前
Python的一些内置模块
python
NPE~2 小时前
RAG 实战教程:从零构建企业智能问答 Agent
python·ai·实战·rag·agent搭建
BaoshengTT2 小时前
2026年双流区汽车贴膜种类及挑选要点保圣威固7V不凡门店
python
zhiSiBuYu05173 小时前
Flask 请求与响应新手实战指南
后端·python·flask