面试复盘
根据传入的文件类型自动判断执行阶段:
- 传入音频文件(.wav / .m4a / .mp3)或视频文件(.mp4)→ 执行阶段一:本地转录
- 传入 PDF 文件(已有转录文本)→ 执行阶段二:整理并输出结果
注意: Claude Code 不支持直接上传音频/视频文件。请粘贴文件路径,或截图文件所在位置(如 Finder 窗口底部会显示完整路径),例如:
帮我整理这场面试 /Users/xxx/Desktop/某公司二面.mp3
多文件处理: 如果用户传入多个音频/视频文件,在阶段一完成后自动执行阶段零(多文件合并排序),再进入阶段二。单文件则跳过合并直接处理。
前置依赖
使用前确保以下工具已安装:
# 转录(二选一,按平台选)
pip install mlx-whisper # Apple Silicon Mac 用,GPU 加速,推荐
pip install openai-whisper # 其他平台 fallback
# 音视频处理(whisper 内部依赖 ffmpeg 解码音频)
# macOS
brew install ffmpeg
# Ubuntu / Debian
sudo apt install ffmpeg
# Windows
# 前往 https://ffmpeg.org/download.html 下载,或用 winget install ffmpeg
平台说明:
- Apple Silicon Mac(M1/M2/M3/M4)→ 使用 mlx-whisper,速度快 5-10 倍
- Intel Mac / Windows / Linux → 自动 fallback 到 openai-whisper(CPU 模式)
飞书配置
若选择保存到飞书,需要先完成以下一次性配置。选本地保存可跳过此章节。
第一步:创建飞书自建应用
- 打开 open.feishu.cn,登录你的飞书账号
- 点击右上角「开发者后台」→「创建应用」→ 选「自建应用」
- 填写应用名称(随意,如
interview-debrief)和描述,创建完成
第二步:开启权限
- 进入应用后,左侧菜单点「权限管理」
- 点击「批量导入/导出权限」→「导入」,将以下 JSON 粘贴进去,点「下一步,确认新增权限」:
{ "scopes": { "user": [ "docx:document", "bitable:app", "docs:doc", "docs:doc:readonly", "docs:document:import", "drive:drive", "drive:file", "wiki:wiki" ] } } - 确认后权限全部生效(测试企业环境下免审核)
第三步:添加回调地址
- 左侧菜单点「安全设置」
- 找到「重定向 URL」,点击添加,填入:
http://localhost:9998/callback - 保存
第四步:发布应用
- 左侧菜单点「版本管理与发布」→「创建版本」
- 版本号填
1.0.0,更新说明随意填 - 可用范围保持默认(部分成员,仅自己可见)
- 点「保存」,飞书个人版免审核,保存后立即生效
第五步:获取凭证并创建配置文件
-
左侧菜单点「凭证与基础信息」,复制 App ID 和 App Secret
-
在终端运行以下命令创建配置文件(替换为你的实际值):
cat > ~/.claude/feishu_config.json << 'EOF' { "app_id": "你的 App ID", "app_secret": "你的 App Secret", "interview_bitable_app_token": "", "interview_bitable_table_id": "" } EOF -
多维表格配置(二选一):
- 没有表格:两个字段留空,第一次运行时 skill 会自动创建并写入
- 已有表格:打开飞书多维表格,URL 格式为
https://xxx.feishu.cn/base/ClG9xxxxxxx?table=tblxxxxxxx,ClG9xxxxxxx填入interview_bitable_app_token,tblxxxxxxx填入interview_bitable_table_id
配置完成后,第一次使用时会自动打开浏览器完成飞书 OAuth 授权,之后 token 自动缓存,无需重复操作。
阶段一:音频/视频处理
触发条件: 文件扩展名为 .wav、.m4a、.mp3、.mp4
检测已有转录文件
收到音频/视频文件路径后,先检查同目录下是否存在同名 .txt 文件(如 interview.m4a → 检查 interview.txt):
- 存在:直接读取
.txt内容,跳过转录,进入阶段二信息收集 - 不存在:启动转录(见下方)
转录
先检测平台,选择对应的转录方式。使用 run_in_background=True 运行转录脚本,转录在后台进行,Claude 立即在对话框中询问阶段二所需的信息(保存方式、日期、公司名等)。收到通知转录完成后,直接用已收集的信息进入阶段零(多文件)或阶段二(单文件)。
import platform, subprocess, os
def is_apple_silicon():
return platform.system() == "Darwin" and platform.machine() == "arm64"
def transcribe(files):
for f in files:
out = f.rsplit('.', 1)[0] + '.txt'
if is_apple_silicon():
# mlx-whisper:Apple Silicon GPU 加速,41 分钟音频约 3-5 分钟
import mlx_whisper
result = mlx_whisper.transcribe(
f, path_or_hf_repo="mlx-community/whisper-large-v3-turbo", language="zh"
)
else:
# openai-whisper:有 NVIDIA GPU 自动用 CUDA,否则 CPU
import whisper, torch
model = whisper.load_model("small")
fp16 = torch.cuda.is_available()
result = model.transcribe(f, language="zh", fp16=fp16)
open(out, 'w', encoding='utf-8').write('\n'.join(s['text'].strip() for s in result['segments']))
print('完成:', out)
注意:
- mlx-whisper 首次运行会自动下载模型(约 800MB),之后缓存本地
- 转录脚本必须用
run_in_background=True启动,否则 Claude 会阻塞无法收集信息
阶段零:多文件合并排序(自动触发)
触发条件: 阶段一产出 2 个及以上 .txt 转录文件时自动执行,无需用户操作。
执行步骤:
- 读取所有 .txt 文件内容
- 根据文件名时间戳 + 内容语义判断顺序:
- 文件名含时间戳(如 1733、215639)→ 按时间升序排列
- 内容含开场白(自我介绍、你好)→ 排最前
- 内容含道别(拜拜、谢谢)→ 排最后
- 内容有明显上下文衔接 → 按语义连贯性判断
- 将排序结果告知用户确认(列出文件顺序和判断依据),确认后进入阶段二
- 若顺序不确定,询问用户
阶段二:面试整理 → 输出结果
触发条件: 文件扩展名为 .pdf,或阶段一/阶段零完成后的转录文本
启动前分两步收集信息:
第一问(单独问):
- 保存方式:本地文件 还是 飞书文档?
收到回答后,若选飞书,检查 ~/.claude/feishu_config.json 是否存在且 app_id / app_secret 均为非空非占位符(不等于「你的 App ID」)。若未配置或配置不完整,先引导完成飞书配置(见「飞书配置」章节),配置完成后再继续。
第二问(合并成一条消息):
- 日期(格式:YYYY-MM-DD)
- 公司名、岗位名、面试轮次(如一面、二面、终面)
- 面试官姓名(选填)、面试时长(选填,如 45min)
默认按面试类型处理。若用户说明是「其他」类型(如聊天、咨询等),则改为询问对方身份和主题,且跳过多维表格更新。
根据类型生成对应的文档标题和信息头:
面试:
{公司名} 完整对话
{公司名} 问答整理
{公司名} 面试总评
信息头:公司 / 岗位 / 轮次 / 日期
其他:
{对方身份} 完整对话
{对方身份} 问答整理
{对方身份} 总评
信息头:对方身份 / 主题 / 日期
1. 本地保存(选本地时执行)
将整理结果写入录音文件同目录下的 .md 文件:
import os
def save_local(source_path, title, full_dialogue, qa, summary):
# source_path 可以是音频文件或 PDF 文件路径
base_dir = os.path.dirname(os.path.abspath(source_path))
for suffix, content in [("完整对话", full_dialogue), ("问答整理", qa), ("总评", summary)]:
out = os.path.join(base_dir, f"{title}-{suffix}.md")
with open(out, "w", encoding="utf-8") as f:
f.write(content)
print(f"已保存:{out}")
保存完成后,将总评内容直接输出在对话框中。后续飞书相关步骤全部跳过。
2. 获取飞书 user_access_token(选飞书时执行)
从配置中读取 APP_ID / APP_SECRET,TOKEN_CACHE 路径做 ~ 展开:
import json, time, os, subprocess, tempfile
import http.server, threading, webbrowser, urllib.parse
# 从本地配置文件读取(~/.claude/feishu_config.json),不写入 skill 避免泄露
_cfg = json.load(open(os.path.expanduser("~/.claude/feishu_config.json")))
APP_ID = _cfg["app_id"]
APP_SECRET = _cfg["app_secret"]
TOKEN_CACHE = os.path.expanduser("~/.claude/feishu_token.json")
REDIRECT_URI = "http://localhost:9998/callback"
def load_token():
try:
with open(TOKEN_CACHE) as f:
return json.load(f)
except:
return None
def save_token(data):
os.makedirs(os.path.dirname(TOKEN_CACHE), exist_ok=True)
with open(TOKEN_CACHE, "w") as f:
json.dump(data, f)
def curl_post_auth(url, data, headers=None):
payload = json.dumps(data, ensure_ascii=False)
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
f.write(payload)
fname = f.name
cmd = ["curl", "-sk", "-X", "POST", url, "--noproxy", "*",
"-H", "Content-Type: application/json"]
if headers:
for h in headers:
cmd += ["-H", h]
cmd += ["--data", f"@{fname}"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
finally:
os.unlink(fname)
if not result.stdout.strip():
return {}
return json.loads(result.stdout)
def get_app_token():
d = curl_post_auth(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
{"app_id": APP_ID, "app_secret": APP_SECRET})
return d.get("tenant_access_token")
def oauth_flow():
# 先释放端口,防止上次未完成进程占用
subprocess.run("lsof -ti:9998 | xargs kill -9 2>/dev/null", shell=True)
app_token = get_app_token()
code_holder = {}
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
code_holder["code"] = params.get("code", [None])[0]
self.send_response(200)
self.end_headers()
self.wfile.write(b"<html><body>Done! You can close this tab.</body></html>")
def log_message(self, *args): pass
server = http.server.HTTPServer(("localhost", 9998), Handler)
t = threading.Thread(target=server.handle_request)
t.start()
auth_url = (f"https://open.feishu.cn/open-apis/authen/v1/authorize"
f"?app_id={APP_ID}&redirect_uri={urllib.parse.quote(REDIRECT_URI)}"
f"&scope=docx%3Adocument%20bitable%3Aapp%20docs%3Adoc%20docs%3Adoc%3Areadonly"
f"%20docs%3Adocument%3Aimport%20drive%3Adrive%20drive%3Afile%20wiki%3Awiki"
f"&response_type=code")
print("正在打开飞书授权页面...")
webbrowser.open(auth_url)
t.join(timeout=120)
code = code_holder.get("code")
if not code:
raise Excepti