Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
- Release 产物、版本命名和校验文档
- Dependabot、CODEOWNERS、开发者启动文档和统一文档中心
- 完全虚构的 Demo 简历、岗位和分析示例数据
- 四平台只读能力注册表与统一 AI 分析入口;Boss / 智联为一级链,猎聘 / 51job 为二级兼容链
- Flyway V9 沟通草稿、猎聘 / 51job AI 字段及投递话术快照迁移
- Boss / 智联 AI 沟通助手:来源展示、人工编辑、复制、恢复原稿和单个 / 批量确认预览
- 本机操作令牌、`expectedUpdatedAt` 乐观并发和确认话术快照校验

### Changed

Expand All @@ -30,12 +34,14 @@
- Dependabot 默认只自动提交 Minor 与 Patch 更新,避免未经评估的大版本升级
- 中英文 README 增加 CodeQL、Release、下载说明和文档中心入口
- 安全文档增加自动化检查和 Release 数据边界
- 智联跨扫描去重不再受 `scanRunId` 限制,保留已有投递和 AI 状态
- 猎聘 / 51job 普通启动改为只读采集;真实投递模式需要显式请求与二次确认头

### 计划中

- 将 Demo 示例数据接入独立、可重置且不触发真实投递的离线 Demo 模式
- 提供不依赖开发环境的完整 Windows 发行包
- 统一智联、猎聘和 51job 的平台适配层
- 补齐猎聘 / 51job 沟通助手前端入口和旧记录的统一 runId
- 继续拆分其他平台分析页面的 hooks 与 components
- 补充真实界面截图和操作 GIF
- 继续将旧兼容 DDL 迁移到 Flyway
Expand Down
19 changes: 11 additions & 8 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ AI JobPilot brings these steps into a local workspace:

## Platform support

| Platform | Collection | AI analysis | Human review | Current status |
| --- | :---: | :---: | :---: | --- |
| Boss Zhipin | ✅ | ✅ | ✅ | Main Chrome Bridge flow; limited API POC and page fallback collection |
| Zhaopin | ✅ | ✅ | ✅ | Main Chrome Bridge flow |
| Liepin | 🟡 | ✅ | ✅ | Basic local flow; adapter work is ongoing |
| 51job | 🟡 | ✅ | ✅ | Basic local flow; adapter work is ongoing |
| Platform | Quality tier | Collection | AI analysis / review queue | Formal execution mode |
| --- | --- | --- | :---: | --- |
| Boss Zhipin | Tier 1 | Chrome Bridge; limited API POC and page fallback | ✅ | Chrome Bridge |
| Zhaopin | Tier 1 | Chrome Bridge | ✅ | Chrome Bridge |
| Liepin | Tier 2 compatibility | Legacy Playwright, read-only collection by default | ✅ | Legacy Playwright with explicit real-action confirmation |
| 51job | Tier 2 compatibility | Legacy Playwright, read-only collection by default | ✅ | Legacy Playwright with explicit real-action confirmation |

`✅` means the current primary workflow is supported. `🟡` means basic functionality exists but coverage and stability still need validation.
The read-only `GET /api/platforms/capabilities` endpoint exposes the current capability declarations. Automated coverage does not replace a small real-account smoke test that stops at the review queue.

Boss and Zhaopin review cards also expose the final communication draft, its source, editing, copying, and AI-original restore. User edits are stored separately from AI output. Single and batch confirmation validate the exact reviewed text before creating a browser task; this does not add a separate automatic messaging entry point.

## Downloads and releases

Expand Down Expand Up @@ -120,9 +122,10 @@ Open the workspace and health endpoint:
```text
Frontend: http://localhost:6866
Backend health: http://localhost:8888/api/health
Backend readiness: http://localhost:8888/api/ready
```

The basic setup is ready when the dashboard checks pass and the health endpoint returns `UP`.
`/api/health` returning `UP` only proves that the process is alive. The workspace is ready for business operations only when the dashboard checks pass and `/api/ready` reports readiness for the database, schema, and task queue.

See [WINDOWS_SETUP.md](WINDOWS_SETUP.md) for the full beginner setup and troubleshooting guide.

Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ Boss 直聘和智联招聘支持 Chrome Bridge,复用你已登录的 Chrome

## 平台支持

| 平台 | 岗位采集 | AI 分析 | 人工确认 | 当前定位 |
| --- | :---: | :---: | :---: | --- |
| Boss 直聘 | | ✅ | ✅ | Chrome Bridge 主要流程;含受限 API POC 与页面降级采集 |
| 智联招聘 | | | ✅ | Chrome Bridge 主要流程 |
| 猎聘 | 🟡 | — | 🟡 | 旧 Playwright 兼容流程;尚未接入统一 AI 分析与待确认队列 |
| 前程无忧 51job | 🟡 | — | 🟡 | 旧 Playwright 兼容流程;尚未接入统一 AI 分析与待确认队列 |
| 平台 | 质量等级 | 岗位采集 | AI 分析 / 待确认 | 正式执行模式 |
| --- | --- | --- | :---: | --- |
| Boss 直聘 | 一级日常链 | Chrome Bridge;含受限 API POC 与页面降级采集 | ✅ | Chrome Bridge |
| 智联招聘 | 一级日常链 | Chrome Bridge | ✅ | Chrome Bridge |
| 猎聘 | 二级兼容链 | 旧 Playwright,默认只读采集 | ✅ | 旧 Playwright;真实投递必须显式二次确认 |
| 前程无忧 51job | 二级兼容链 | 旧 Playwright,默认只读采集 | ✅ | 旧 Playwright;真实投递必须显式二次确认 |

`✅` 表示当前主流程支持,`🟡` 表示已有基础能力但稳定性和覆盖范围仍需继续验证,`—` 表示尚未接入该统一流程
`✅` 表示代码与自动化契约已经接通,不代表当前账号和当天页面已经完成真实 smoke。四个平台的当前声明可通过只读接口 `GET /api/platforms/capabilities` 查看;招聘网站改版后仍应先小范围验证到“待确认”

## 下载与版本

Expand Down Expand Up @@ -177,6 +177,18 @@ Boss 直聘和智联招聘推荐使用 Chrome Bridge:

扩展只负责辅助本地流程,不会绕过登录验证、验证码或平台投递限制。

## AI 沟通助手

Boss 和智联的待确认岗位现在会显示最终沟通话术及其来源,并支持编辑、复制和恢复 AI 原稿。话术优先级固定为:

```text
人工编辑稿 → AI greeting → 当前档案默认话术 → 空白警告
```

人工稿与 AI 原稿分开保存,重新分析岗位不会覆盖人工编辑。保存接口使用只在本机内存中存在的操作令牌和 `expectedUpdatedAt` 并发校验;单个、批量确认和显式重试还会再次校验页面预览快照,话术变化时会停止并要求重新确认。

该能力不会增加独立的自动发消息入口。Boss 的确认任务会把核对后的话术交给现有 Chrome Bridge;智联当前把话术随任务保存并提供复制,但现有扩展不会自动发送聊天消息。

## 手动开发启动

后端:
Expand Down Expand Up @@ -253,9 +265,9 @@ db/getjobs.db

## 项目状态

最新正式版本为 `1.3.0`。当前 `codex/stable-v1-completion` 分支处于稳定 V1 验收阶段,重点是验证 Boss / 智联 Chrome Bridge 主链、猎聘 / 51job 兼容链、数据库迁移和人工确认边界
最新正式版本为 `1.3.0`。`codex/stable-v1-completion` 已作为待验收基线整理为面向 `main` 的 PR;当前功能分支已完成四平台能力注册表、统一 AI 分析契约和 Boss / 智联 AI 沟通助手的代码与自动化验证

仓库现已具备后端与前端 CI、Chrome 扩展校验、Docker 配置校验、CodeQL 安全扫描、Dependabot 依赖维护和 Release 产物校验。下一阶段先完成四平台分级能力与真实 smoke,再把已有 AI `greeting` 做成可见、可编辑、人工确认的沟通草稿;不会新增绕过人工确认的自动发送入口。详见 [ROADMAP.md](ROADMAP.md)。
真实平台 smoke 仍未执行:Boss / 智联、猎聘 / 51job 都需要在操作当时由用户明确确认,并且第一轮只运行到待确认队列。验收前不会合并 `main`,也不会关闭被替代分支。详见 [ROADMAP.md](ROADMAP.md)。

## 参与贡献

Expand Down
24 changes: 13 additions & 11 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,27 @@
- [x] 在数据库副本上执行 V8 迁移演练并保护原库指纹
- [ ] Boss / 智联真实验证到待确认队列,不执行真实投递
- [ ] 猎聘 / 51job 验证现有采集、Profile 隔离和错误恢复
- [ ] 将稳定 V1 完整差异收口为面向 `main` 的可审查 PR
- [ ] 校正文档、版本状态和平台能力说明
- [x] 将稳定 V1 完整差异收口为面向 `main` 的可审查 PR(PR #42,尚未合并)
- [x] 校正文档、版本状态和平台能力说明

## Next:四平台分级闭环

- [ ] 建立统一平台能力注册表;Boss / 智联为一级质量,猎聘 / 51job 为二级兼容质量
- [ ] 将四平台接入统一 AI 分析和待确认语义,同时保留各自采集实现
- [ ] 为每个平台声明唯一正式执行模式,避免 Chrome Bridge 与旧 Worker 重复执行
- [ ] 统一 `profileId + runId + requestKey` 关联信息和失败语义
- [ ] 修复跨扫描岗位去重差异,并建立脱敏回归夹具
- [x] 建立统一平台能力注册表;Boss / 智联为一级质量,猎聘 / 51job 为二级兼容质量
- [x] 将四平台接入统一 AI 分析和待确认语义,同时保留各自采集实现
- [x] 为每个平台声明唯一正式执行模式;猎聘 / 51job 的普通启动默认降级为只读采集
- [ ] 完成全部 `profileId + runId + requestKey` 关联信息(Boss / 智联已串联;旧平台历史记录仍缺统一 runId)
- [x] 修复智联跨扫描岗位去重差异,并建立脱敏回归测试

## After:AI 沟通助手

复用已有 AI `greeting`,先做可见、可编辑、可复制和人工确认,不增加独立自动发送入口:

- [ ] Boss / 智联展示 AI 原稿、人工草稿和明确来源
- [ ] 人工草稿独立持久化,AI 重新分析不得覆盖用户编辑
- [ ] 单个和批量确认前展示最终话术
- [ ] 猎聘 / 51job 完成统一分析后复用相同能力
- [x] Boss / 智联展示 AI 原稿、人工草稿和明确来源
- [x] 人工草稿独立持久化,AI 重新分析不得覆盖用户编辑
- [x] 单个和批量确认前展示最终话术,并校验预览快照未被并发改变
- [ ] 猎聘 / 51job 完成统一分析后复用相同能力(后端草稿契约已支持,前端入口待真实 smoke 后补齐)

当前代码门禁和 V9 数据库副本演练已经通过,但“每日可用核心”仍必须等四个平台在正确 Profile 下完成真实、只到待确认队列的 smoke 后才能宣布完成。

## Later:首次体验与求职复盘

Expand Down
18 changes: 14 additions & 4 deletions doc/使用指南.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

本文面向希望直接运行投递牛马的使用者,覆盖环境准备、启动方式、网页端配置、平台使用和常见问题。

当前项目阶段:`1.3.x 稳定 V1 验收阶段`。当前推荐工作流是配置检查 → 扫描岗位 → AI 分析 → 待确认 → 用户确认后投递 → 状态回写 → 分析统计。Boss / 智联是 Chrome Bridge 主链,猎聘 / 51job 暂时是旧 Playwright 兼容链。
当前项目阶段:`1.3.x 稳定 V1 验收阶段`。当前推荐工作流是配置检查 → 扫描岗位 → AI 分析 → 待确认 → 核对沟通话术 → 用户确认后投递 → 状态回写 → 分析统计。Boss / 智联是一级 Chrome Bridge 主链,猎聘 / 51job 是二级旧 Playwright 兼容链。

## 运行前准备

Expand Down Expand Up @@ -121,11 +121,19 @@ Boss 和智联在 1.3 版本继续使用并增强 Chrome Bridge 路线。它会
4. 扩展会打开或复用平台标签页,采集岗位列表和详情。
5. 后端收到岗位后执行 AI 分析,命中岗位会进入“待确认”。
6. 到对应平台的岗位分析页,筛选 `待确认`。
7. 点击单个 Chrome 投递,或批量投递当前筛选的待确认岗位
7. 先核对每个岗位最终将使用的沟通话术,再点击单个 Chrome 投递或批量确认当前筛选岗位
8. 投递完成后,状态会写回为 `已投递` 或 `投递失败`。

实际投递前请看清确认弹窗。建议每次先少量投递,确认账号没有触发安全验证后再继续。

### 沟通话术编辑与确认

- Boss / 智联待确认卡片会显示话术来源:人工编辑稿、AI 原稿、档案默认话术或空白警告。
- 点击“编辑沟通语”可以修改、复制或恢复 AI 原稿;人工稿会独立保存,重新分析岗位不会覆盖它。
- 最终优先级是:人工编辑稿 → AI 原稿 → 当前档案默认话术 → 空白警告。
- 单个确认、批量确认和显式重试都会再次显示最终话术。如果另一页面已经修改草稿,后端会拒绝旧预览并要求刷新。
- Boss 会把确认后的话术交给 Chrome Bridge;智联当前提供保存和复制,扩展不会自动发送聊天消息。

## AI 配置

AI 配置主要用于 Boss 直聘岗位匹配和打招呼语生成。常见字段包括:
Expand Down Expand Up @@ -165,13 +173,15 @@ MODEL=gpt-5-nano

### 猎聘

- 支持扫码登录、岗位搜索、筛选和投递。
- 支持扫码登录、岗位搜索、筛选、统一 AI 分析和待确认状态。
- 页面“开始只读采集”不会执行投递;旧 Playwright 真实投递只保留为显式兼容模式。
- 通常需要在猎聘 App 里配置默认打招呼语。
- 原项目说明中提到猎聘更适合批量打招呼,但主动发消息可能存在上限。

### 51job

- 支持配置、登录、投递、统计和结果列表。
- 支持配置、登录、只读采集、统一 AI 分析、待确认状态、统计和结果列表。
- 页面普通启动不会执行投递;旧 Playwright 真实投递只保留为显式兼容模式。
- 平台岗位质量和投递上限可能变化,建议小批量运行并观察结果。

### 智联招聘
Expand Down
24 changes: 23 additions & 1 deletion front/app/51job/analysis/AnalysisContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
}

type Job51Item = {
id?: number
jobId: number
companyName?: string
jobName?: string
Expand All @@ -50,6 +51,9 @@
createdAt?: string
industry?: string
companyScale?: string
aiScore?: number
aiDecision?: string
aiReason?: string
}

type PagedResult51 = {
Expand Down Expand Up @@ -92,7 +96,7 @@

export default function AnalysisContent({ showHeader = false }:{ showHeader?: boolean }) {
const [stats,setStats]=useState<StatsResponse|null>(null)
const [loadingStats,setLoadingStats]=useState(true)

Check warning on line 99 in front/app/51job/analysis/AnalysisContent.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'loadingStats' is assigned a value but never used
const [items,setItems]=useState<Job51Item[]>([])
const [total,setTotal]=useState(0)
const [page,setPage]=useState(1)
Expand All @@ -111,10 +115,11 @@
const [reloading,setReloading]=useState(false)
const [exporting,setExporting]=useState(false)
const [recoveringJobId,setRecoveringJobId]=useState<number|null>(null)
const [analyzingJobId,setAnalyzingJobId]=useState<number|null>(null)

const statusOptions = ["未投递", "投递确认中", "投递结果待确认", "已投递", "投递失败"]
const statusOptions = ["待确认", "AI分析中", "AI不匹配", "AI分析失败", "未投递", "投递确认中", "投递结果待确认", "已投递", "投递失败"]

useEffect(()=>{ loadStats() },[])

Check warning on line 122 in front/app/51job/analysis/AnalysisContent.tsx

View workflow job for this annotation

GitHub Actions / Frontend

React Hook useEffect has a missing dependency: 'loadStats'. Either include it or remove the dependency array
useEffect(()=>{ setInputPage(page) },[page])
useEffect(()=>{ setInputSize(size) },[size])

Expand Down Expand Up @@ -146,7 +151,7 @@
try{ setLoadingStats(true); const res = await fetch(`${API_BASE}/api/51job/stats?${params.toString()}`); const data:StatsResponse = await res.json(); setStats(data) }catch(e){ console.error("fetch stats failed",e) } finally { setLoadingStats(false) }
}

useEffect(()=>{ loadList(1,size) },[])

Check warning on line 154 in front/app/51job/analysis/AnalysisContent.tsx

View workflow job for this annotation

GitHub Actions / Frontend

React Hook useEffect has missing dependencies: 'loadList' and 'size'. Either include them or remove the dependency array

const onReload = async ()=>{
try{ setReloading(true); const res=await fetch(`${API_BASE}/api/51job/reload`); const data=await res.json(); console.log("reload",data); await loadList(1,size); await loadStats() }catch(e){ console.error("reload failed",e) } finally { setReloading(false) }
Expand Down Expand Up @@ -178,6 +183,19 @@
}
}

const analyzeJob = async (job:Job51Item)=>{
if (!job.id){ alert("该 51job 岗位缺少内部 ID,无法加入统一 AI 分析队列。"); return }
try{
setAnalyzingJobId(job.id)
const response=await fetch(`${API_BASE}/api/platforms/51job/jobs/${job.id}/analyze`,{ method:"POST" })
const data=await response.json().catch(()=>({}))
if (!response.ok || data.success===false) throw new Error(data.message||"加入 AI 分析队列失败")
alert(data.queued ? "已加入统一 AI 分析队列。" : (data.message||"该岗位已有分析任务。"))
await loadList(page,size)
}catch(error){ alert(error instanceof Error ? error.message : "加入 AI 分析队列失败") }
finally{ setAnalyzingJobId(null) }
}

const exportCSV = async ()=>{
try{ setExporting(true)
const baseParams = new URLSearchParams()
Expand Down Expand Up @@ -303,6 +321,10 @@
<td className="px-4 py-3 text-sm leading-6 align-top"><div className="truncate" title={it.hrName||'-'}>{it.hrName||'-'}</div></td>
<td className="px-4 py-3 text-sm leading-6 align-top">
<span className={`whitespace-nowrap px-2 py-1 rounded-full text-xs ${ (it.deliveryStatus||'').includes('已投递') ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300' : 'bg-slate-100 text-slate-700 dark:bg-slate-800/50 dark:text-slate-300' }`}>{it.deliveryStatus||'-'}</span>
<div className="mt-1 text-xs text-muted-foreground">AI {it.aiScore??'-'} · {it.aiDecision||'未分析'}</div>
{["未投递","AI分析失败","LIST_COLLECTED"].includes((it.deliveryStatus||"").trim()) && (
<div className="mt-2"><Button size="sm" variant="outline" disabled={analyzingJobId===it.id} onClick={()=>analyzeJob(it)}>{analyzingJobId===it.id?"入队中...":"AI 分析"}</Button></div>
)}
{it.deliveryStatus === "投递结果待确认" && (
<div className="mt-2 flex flex-wrap gap-1">
<Button size="sm" variant="outline" disabled={recoveringJobId===it.jobId} onClick={()=>recoverDelivery(it.jobId,"confirmed")}>核对已投递</Button>
Expand Down
6 changes: 3 additions & 3 deletions front/app/51job/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
console.log('[51job SSE] 🔌 关闭SSE连接')
client.close()
}
}, [backendAvailable])

Check warning on line 115 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

React Hook useEffect has a missing dependency: 'cookieSavedAfterLogin'. Either include it or remove the dependency array

// 解析/序列化关键词(与猎聘保持一致)
const parseKeywordsFromDb = (raw?: string): string => {
Expand Down Expand Up @@ -150,7 +150,7 @@
if (Array.isArray(arr) && arr.length > 0) {
return String(arr[0] ?? '').trim()
}
} catch (_) {

Check warning on line 153 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'_' is defined but never used
// ignore, fall through
}
}
Expand Down Expand Up @@ -212,12 +212,12 @@
} else {
setLoadingConfig(false)
}
} catch (e) {

Check warning on line 215 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'e' is defined but never used
setBackendAvailable(false)
setLoadingConfig(false)
}
})()
}, [])

Check warning on line 220 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

React Hook useEffect has a missing dependency: 'fetchAllData'. Either include it or remove the dependency array

const handleStartDelivery = async () => {
try {
Expand All @@ -239,7 +239,7 @@
const response = await fetch(`${API_BASE}/api/51job/stop`, { method: 'POST' })
const data = await response.json()
if (data.success) setIsDelivering(false)
} catch (error) {}

Check warning on line 242 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'error' is defined but never used
}

const triggerLogout = async () => {
Expand All @@ -249,13 +249,13 @@
setIsLoggedIn(false)
setLogoutResult({ success: data.success, message: data.success ? '已退出登录,Cookie已清空。' : data.message })
setShowLogoutResultDialog(true)
} catch (error) {

Check warning on line 252 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'error' is defined but never used
setLogoutResult({ success: false, message: '退出登录失败:网络或服务异常。' })
setShowLogoutResultDialog(true)
}
}

const handleSaveCookie = async () => {

Check warning on line 258 in front/app/51job/page.tsx

View workflow job for this annotation

GitHub Actions / Frontend

'handleSaveCookie' is assigned a value but never used
try {
const response = await fetch(`${API_BASE}/api/cookie/save?platform=51job`, { method: 'POST' })
const data = await response.json()
Expand Down Expand Up @@ -338,11 +338,11 @@
</Button>
) : isDelivering ? (
<Button onClick={handleStopDelivery} size="sm" className="app-button-danger px-4">
<BiStop className="mr-1" /> 停止投递
<BiStop className="mr-1" /> 停止采集
</Button>
) : (
<Button onClick={handleStartDelivery} size="sm" className="app-button-success px-4">
<BiPlay className="mr-1" /> 开始投递
<BiPlay className="mr-1" /> 开始只读采集
</Button>
)}
<Button onClick={() => setShowLogoutDialog(true)} size="sm" className="app-button-danger px-4">
Expand Down Expand Up @@ -373,7 +373,7 @@
<CardContent>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">请在浏览器标签页中登录 51job 平台,登录成功后系统会自动检测登录状态。</p>
<p className="text-sm text-muted-foreground">登录成功后,点击“开始投递”按钮启动自动投递任务。</p>
<p className="text-sm text-muted-foreground">登录成功后,点击“开始只读采集”获取岗位;该按钮不会执行真实投递。</p>
<p className="text-sm text-muted-foreground">点击“保存配置”按钮可手动保存当前登录相关信息到数据库。</p>
</div>
</CardContent>
Expand Down
Loading
Loading