ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

200行代码实现AI工时助手:浏览器插件+本地大模型自动生成周报

200行代码实现AI工时助手:浏览器插件+本地大模型自动生成周报 1. 项目缘起从“周报地狱”到代码救赎每周五下午你是不是也和我一样对着空白的周报文档发呆努力回想这周到底干了啥项目A的会议开了多久那个棘手的Bug到底花了多少时间才解决这种“周报焦虑”几乎是每个职场人的通病。手动记录工时不仅繁琐而且往往不准确等到周末汇总时记忆早已模糊。我也曾尝试过各种现成的工时管理软件但它们要么功能臃肿、操作复杂要么无法与我日常的工作流比如浏览器、IDE无缝结合最后都不了了之。直到我开始接触本地化的大语言模型LLM一个想法冒了出来能不能让AI帮我自动记录工作内容并生成结构化的工时日志这个想法催生了这个项目——一个仅用200行左右代码实现的浏览器插件形态的AI工时助手。它的核心思路很简单静默记录你在不同标签页对应不同工作内容的活跃时间然后利用本地的Ollama大模型将浏览历史或简单的笔记“翻译”成专业的周报条目。整个过程完全在本地运行无需担心数据隐私也无需为额外的SaaS服务付费。这个项目特别适合以下几类朋友厌倦了手工记录时间的开发者、追求效率极致的产品经理、以及任何希望将重复性文书工作自动化从而更专注于核心工作的知识工作者。它不是一个功能庞杂的全套时间管理方案而是一个精准解决“周报工时记录”这一痛点的轻量级工具。接下来我将从设计思路到每一行代码为你完整拆解这个“小而美”的解决方案。2. 整体设计插件本地AI的极简架构为什么选择“浏览器插件 Ollama”这个技术组合这是经过深思熟虑的。工时记录的关键在于无感它必须融入你现有的工作环境而不是让你额外打开一个应用去操作。浏览器是现代知识工作的主战场无论是查文档、写代码Cloud IDE、沟通协作还是项目管理大部分时间都在浏览器中度过。因此浏览器插件是捕获“工作上下文”最自然的入口。而选择Ollama来运行本地大模型则主要基于隐私、成本和可控性三大考量。工时数据可能涉及工作内容详情上传到第三方AI服务存在隐私风险。Ollama允许我在自己的电脑上运行诸如Llama 3、Qwen等优秀的开源模型数据不出本地彻底放心。其次一次部署无限次使用没有API调用费用。最后本地模型响应速度快且不受网络波动影响。整个系统的架构非常清晰主要由两部分组成浏览器插件前端负责“感知”。它监听浏览器的标签页活动记录你在每个域名或特定URL上的停留时间精确到秒。同时它提供一个简单的弹出窗口Popup让你可以手动输入关键工作笔记或一键触发周报生成。本地AI服务后端负责“思考”。一个用Python写的简易HTTP服务器通过Ollama提供的API与本地大模型对话。它接收插件发来的原始时间记录和笔记理解后将其组织成“任务名称耗时 - 描述”的标准周报格式。两者通过浏览器插件可访问的localhost端口进行通信。这种松耦合的设计好处明显插件部分轻量专注于数据采集AI部分独立可以随时替换或升级模型甚至未来可以扩展为支持多个AI服务提供商。注意这个设计的前提是你的主要工作在浏览器内完成。如果你的核心工作深度依赖本地IDE如VS Code、IntelliJ则需要通过IDE插件或系统级监控来补充数据源这将是项目的一个潜在扩展方向。3. 核心细节解析如何精准捕获与理解工时3.1 工时捕获的逻辑与精度权衡浏览器插件如何知道你在“工作”而不是在“摸鱼”这里涉及一个关键概念活动标签页Active Tab和窗口焦点Window Focus。我们只记录用户当前正在浏览的、且其浏览器窗口处于激活状态的标签页时间。这通过监听Chrome API的tabs.onActivated、windows.onFocusChanged等事件来实现。但是直接记录时间会产生大量碎片化数据比如你在两个文档间快速切换。因此需要一个聚合逻辑。我的实现是以“域名”为基本聚合单位。当用户在当前标签页停留超过一个最小时间阈值例如30秒才认为这是一段有效的“工作会话”并将其累加到该域名对应的总工时里。同时我会记录这段时间内该标签页的标题tab.title作为后续AI生成描述的原始素材。这里有一个重要的实操心得最小时间阈值debounce time的设置至关重要。设置太短如5秒会导致记录过于敏感把很多无意义的短暂切换都记下来数据噪音大。设置太长如2分钟又会漏掉一些真正的、快速的高效工作时段。经过反复测试我将这个值设定为45秒。这是一个比较折中的值能过滤掉大部分无意识的跳转又能捕捉到有效的查阅和编辑操作。你可以在插件的配置项里根据自己的工作习惯调整这个值。3.2 本地大模型Ollama的选型与提示词工程Ollama使得在本地运行大模型变得异常简单。模型选型上我推荐从Llama 3 8B或Qwen 7B这样的中等尺寸模型开始。它们对硬件要求相对友好16GB内存的电脑基本可以流畅运行并且在理解指令和文本生成任务上已经表现出足够的能力。项目的核心“智能”在于发给AI的提示词Prompt。我们不能简单地把一堆浏览记录扔给AI说“写个周报”那会得到杂乱无章的结果。我的提示词结构经过精心设计旨在引导AI扮演一个专业的助理角色你是一个专业的工时记录助理。请根据以下原始工作日志生成一份简洁、专业的周报条目。 要求 1. 归纳核心任务用精炼的短语作为任务名。 2. 估算每个任务的耗时基于提供的活跃时间戳。 3. 为每个任务生成一行有价值的描述说明工作内容和成果。 4. 格式为“- [任务名]约[X小时Y分钟] - [描述]”。 原始日志 [日期] [时间范围] 活跃于 [域名/页面标题]笔记“[用户手动输入的笔记]” [日期] [时间范围] 活跃于 [域名/页面标题]...关键点在于数据预处理插件在发送请求前会先将原始的、按秒记录的时间数据按域名和日期进行聚合计算出总时长。然后将聚合后的“域名或页面标题 总时长 用户笔记”作为一条日志发送。这样AI需要处理的是已经初步归类过的信息它的任务是从语义上理解这些信息属于哪个项目或任务并进行文字润色。例如原始数据可能是github.com- 总计2.5小时 - 笔记“修复登录接口的并发问题”confluence.company.com- 总计1小时 - 笔记“撰写项目A的API设计文档”stackoverflow.com- 总计0.5小时 - 笔记“查询Redis缓存雪崩解决方案”AI在接收到这些后可能会生成- [后端开发]约2.5小时 - 修复了用户登录模块在高并发场景下的数据一致性问题涉及接口优化与数据库事务调整。- [技术文档编写]约1小时 - 完成了项目A核心模块的API接口设计文档明确了请求/响应格式与错误码规范。- [技术调研]约0.5小时 - 调研了Redis缓存雪崩的常见成因与预防策略为系统稳定性优化做准备。你可以看到AI不仅格式化了文本还将“GitHub”关联到“后端开发”将“Confluence”关联到“文档编写”甚至为“Stack Overflow”的查询行为赋予了“技术调研”的任务属性这使得周报看起来更专业、更有条理。4. 实操搭建从零到一的完整实现步骤4.1 环境准备与Ollama部署首先我们需要在本地搭建AI引擎。前往Ollama官网下载对应操作系统的安装包。安装过程非常简单几乎是一键完成。安装后打开终端或命令提示符拉取你选择的模型。以Llama 3 8B为例# 拉取模型这步可能需要一些时间取决于你的网络 ollama pull llama3:8b # 运行模型服务并指定一个本地端口例如11434 ollama run llama3:8b # 注意以上命令是交互式运行。作为后端服务我们通常使用其API。 # Ollama默认会在 11434 端口启动一个HTTP API服务。为了确保服务稳定运行我建议创建一个简单的系统服务或使用nohup让它在后台运行。更简单的方法是写一个Python脚本作为我们工时助手的后端这个脚本会负责与Ollama API通信同时也可以管理模型的生命周期。4.2 浏览器插件Manifest V3开发详解我们创建一个新的文件夹ai-worktime-helper里面包含插件必需的文件manifest.json插件的配置文件声明权限和资源。background.js后台服务脚本负责状态监听和核心计时逻辑。popup.htmlpopup.js弹出窗口的界面和交互逻辑。content.js可选的如果需要与页面内容交互例如提取特定内容时才需要本项目暂不需要。manifest.json关键配置{ manifest_version: 3, name: AI工时助手, version: 1.0, description: 自动记录浏览时间并用AI生成周报, permissions: [tabs, storage, activeTab], host_permissions: [http://localhost:*/*], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: AI工时助手 } }我们申请了tabs权限来监听标签页storage权限来本地存储工时数据host_permissions允许插件与本地后端的localhost通信。background.js核心计时逻辑let activeTabId null; let startTime null; let currentDomain null; let timer null; const DEBOUNCE_MS 45000; // 45秒聚合阈值 // 监听标签页切换 chrome.tabs.onActivated.addListener((activeInfo) { updateActiveTab(activeInfo.tabId); }); // 监听标签页更新如URL变化 chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) { if (tab.active changeInfo.url) { updateActiveTab(tabId); } }); async function updateActiveTab(tabId) { clearTimeout(timer); // 清除旧的计时器 // 保存上一段有效工作时间 if (activeTabId ! null startTime ! null currentDomain) { const duration Date.now() - startTime; if (duration 30000) { // 至少30秒才记录避免误触 await saveWorkSession(currentDomain, duration); } } // 获取新标签页信息 const tab await chrome.tabs.get(tabId); if (!tab.url || tab.url.startsWith(chrome://) || tab.url.startsWith(edge://)) { // 忽略浏览器内部页面 resetTracker(); return; } const url new URL(tab.url); const domain url.hostname; // 设置新的追踪起点 activeTabId tabId; currentDomain domain; startTime Date.now(); // 设置一个新的计时器在DEBOUNCE_MS后检查是否仍在同一页面 timer setTimeout(async () { if (activeTabId tabId currentDomain domain) { const duration Date.now() - startTime; await saveWorkSession(domain, duration); // 保存后重置开始时间继续累积下一段 startTime Date.now(); } }, DEBOUNCE_MS); } async function saveWorkSession(domain, durationMs) { const today new Date().toISOString().split(T)[0]; const key worklog_${today}; const data await chrome.storage.local.get([key]); let workLog data[key] ? JSON.parse(data[key]) : {}; if (!workLog[domain]) { workLog[domain] { totalMs: 0, notes: [] }; } workLog[domain].totalMs durationMs; // 可以在这里选择性地保存页面标题 // workLog[domain].lastTitle tabTitle; await chrome.storage.local.set({ [key]: JSON.stringify(workLog) }); console.log(记录: ${domain} - ${Math.round(durationMs/1000)}s); } function resetTracker() { activeTabId null; startTime null; currentDomain null; clearTimeout(timer); }这段代码是插件的“心脏”。它巧妙地使用了一个延时器setTimeout来实现聚合逻辑。只有当用户在同一个标签页持续活跃超过DEBOUNCE_MS45秒才会将这段时间记录为一个有效的工作会话。4.3 Python后端服务与AI集成接下来我们创建后端的Python脚本ai_worktime_server.py。它使用Flask框架创建一个轻量级Web服务器接收插件发来的数据调用Ollama API并返回格式化后的周报文本。首先安装依赖pip install flask requests。from flask import Flask, request, jsonify import requests import json from datetime import datetime app Flask(__name__) # Ollama API 地址 OLLAMA_API_URL http://localhost:11434/api/generate # 使用的模型名称 MODEL_NAME llama3:8b def format_duration(ms): 将毫秒转换为易读的 X小时Y分钟 格式 seconds ms // 1000 hours seconds // 3600 minutes (seconds % 3600) // 60 if hours 0: return f{hours}小时{minutes}分钟 else: return f{minutes}分钟 def build_prompt(worklog_data, user_notes): 构建发送给AI的提示词 today datetime.now().strftime(%Y-%m-%d) prompt_lines [f今天是{today}。请根据以下工作日志生成今日的周报条目。\n] prompt_lines.append(要求归纳任务、估算耗时、生成专业描述。格式为- [任务名]约[X小时Y分钟] - [描述]\n) prompt_lines.append(工作日志) for domain, info in worklog_data.items(): duration_str format_duration(info[totalMs]) # 这里可以加入从info中提取的页面标题 prompt_lines.append(f- 在 {domain} 上工作约{duration_str}。) if info.get(notes): prompt_lines.append(f 备注{; .join(info[notes])}) if user_notes: prompt_lines.append(f\n用户补充说明{user_notes}) prompt_lines.append(\n请生成周报) return \n.join(prompt_lines) app.route(/generate-report, methods[POST]) def generate_report(): try: data request.json worklog data.get(worklog, {}) user_notes data.get(notes, ) if not worklog: return jsonify({error: 无工作日志数据}), 400 # 构建提示词 prompt build_prompt(worklog, user_notes) # 调用Ollama API payload { model: MODEL_NAME, prompt: prompt, stream: False, options: { temperature: 0.2, # 较低的温度让输出更稳定、更专业 top_p: 0.9 } } response requests.post(OLLAMA_API_URL, jsonpayload, timeout60) if response.status_code 200: result response.json() report_text result.get(response, ).strip() # 简单清理AI回复中可能出现的引导词 report_text report_text.replace(以下是生成的周报条目, ).replace(周报条目, ).strip() return jsonify({report: report_text}) else: return jsonify({error: fOllama API错误: {response.status_code}}), 500 except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: # 在本地启动服务端口可自定义如5000 app.run(host127.0.0.1, port5000, debugFalse)这个后端服务的关键函数是build_prompt它负责将插件收集的原始数据域名、耗时、笔记组织成一段清晰的指令文本引导AI进行创作。temperature参数设置为较低的0.2是为了让AI的输出更加确定和专业化减少随机性。4.4 插件前端与后端联调最后我们需要在插件的弹出窗口popup.js中实现数据获取和与后端通信的功能。popup.html(简化版):!DOCTYPE html html body h3AI工时助手/h3 textarea idnotes placeholder补充工作笔记.../textarea button idgenBtn生成今日周报/button div idreport/div script srcpopup.js/script /body /htmlpopup.jsdocument.getElementById(genBtn).addEventListener(click, async () { const notes document.getElementById(notes).value; const today new Date().toISOString().split(T)[0]; const key worklog_${today}; // 从本地存储获取今日数据 const result await chrome.storage.local.get([key]); let worklog {}; if (result[key]) { worklog JSON.parse(result[key]); } if (Object.keys(worklog).length 0) { document.getElementById(report).innerText 今日暂无工作记录。; return; } // 调用本地Python后端 try { const response await fetch(http://localhost:5000/generate-report, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ worklog: worklog, notes: notes }) }); const data await response.json(); if (data.report) { document.getElementById(report).innerText data.report; } else { document.getElementById(report).innerText 错误: ${data.error}; } } catch (error) { document.getElementById(report).innerText 请求失败: ${error.message}. 请确保本地AI服务已启动 (http://localhost:5000)。; } });至此所有核心模块都已就位。将插件文件夹加载到Chrome的“扩展程序管理”页面开启开发者模式确保Python后端服务在运行python ai_worktime_server.py然后你就可以开始使用了。5. 常见问题与排查技巧实录在实际使用和分享过程中我遇到了不少典型问题。这里整理成一份速查表希望能帮你快速排雷。问题现象可能原因排查与解决步骤插件安装后无任何记录1. 后台脚本未正确运行。2. 权限问题或浏览器限制。1. 打开扩展管理页检查插件是否已启用后台服务是否报错点击“背景页”查看控制台。2. 检查manifest.json的permissions是否包含tabs。重启浏览器试试。点击“生成周报”按钮提示“请求失败”或连接错误1. 本地Python后端服务未启动。2. 端口被占用或防火墙阻止。3. 插件host_permissions未配置。1. 在终端运行python ai_worktime_server.py确认服务在127.0.0.1:5000运行。2. 使用curl http://127.0.0.1:5000/测试服务是否可达。修改Flask的app.run端口并同步更新popup.js中的请求URL。3. 确认manifest.json中host_permissions包含http://localhost:5000/*。AI生成的周报内容空洞、不准确或格式错误1. 提示词Prompt设计不佳。2. 模型能力有限或未理解指令。3. 输入给AI的原始数据质量太差如全是无意义域名。1.优化提示词这是最关键的一步。在build_prompt函数中让指令更清晰、更具体。可以加入“避免使用‘处理了’、‘进行了’等模糊词汇”这样的约束。2.尝试不同模型换用qwen:7b或llama3.1:8b等模型可能效果不同。3.预处理输入数据在插件端可以尝试过滤掉一些明显非工作的域名如娱乐、社交网站或者允许用户手动标记某个域名为“工作相关”。Ollama模型下载慢或无法下载网络连接问题特别是从海外拉取模型。1.使用国内镜像这是最有效的方案。配置Ollama使用国内镜像源速度会有质的提升。例如在环境变量中设置OLLAMA_HOST或使用第三方提供的镜像服务。2.手动导入模型在能高速下载的机器上先ollama pull然后使用ollama show --modelfile导出Modelfile再到目标机器上ollama create。工时记录明显偏少或漏记1. 聚合阈值DEBOUNCE_MS设置过高。2. 浏览器窗口失去焦点时未正确处理。1.调整阈值在background.js中将DEBOUNCE_MS调低比如改为2500025秒观察记录是否更符合预期。2.完善焦点监听在background.js中增加对windows.onFocusChanged事件的监听当浏览器窗口失去焦点时立即保存当前计时会话并重置追踪器。插件在Edge/Firefox上无法运行代码基于Chrome扩展API编写其他浏览器可能有细微差异。1.Edge基本兼容直接加载即可。2.Firefox需使用WebExtensions API部分API命名空间不同如browser代替chrome需做适配。manifest.json版本也可能需要调整。几个独家避坑技巧关于模型选择如果你的电脑内存只有8GB运行8B模型可能会比较吃力。可以尝试更小的模型如Phi-3-mini3.8B它在指令跟随上表现也不错对资源要求低得多。生成周报这种任务不一定需要顶级模型。关于数据持久化chrome.storage.local有容量限制。如果你需要长期保存历史记录最好在后端服务里增加一个功能定期将数据导出为JSON或CSV文件或者同步到你自己的笔记软件如通过Obsidian的API。关于“摸鱼”识别这个工具目前是“诚实”的它记录所有活跃标签页。如果你希望它自动过滤娱乐网站可以在background.js的updateActiveTab函数里增加一个黑名单检查。例如如果domain包含youtube.com、bilibili.com等就直接调用resetTracker()不进行记录。提升生成质量在手动输入笔记的文本框里养成输入“关键词”的习惯而不是完整的句子。比如在解决一个Bug时输入“登录超时排查Redis连接池修复配置”。这些关键词能极大地帮助AI理解上下文生成更精准的描述。这个200行代码的项目本质上是一个“粘合剂”它将浏览器的工作场景、本地的数据存储和新兴的本地AI能力巧妙地连接起来解决了一个非常具体的痛点。它不完美但足够有效。最大的成就感来自于每周五一键生成那份结构清晰、内容充实的周报时所省下的那半小时焦灼的回忆时间。技术服务于人让工具变得简单、聪明、无感或许就是最好的方向。你可以基于这个核心框架扩展出更多有趣的功能比如按项目分类、生成月度总结、甚至集成到企业IM自动发送。
返回列表