ericwyuan
28397e9e6a
feat(timeline): 事件时间轴支持删除视频会话
...
三端联动实现(fam-ui -> fam-core -> fam-edge),沿用项目里"NAS 转发写请求
到 Oracle"的既有模式(照抄 identity_correct 那套骨架):
- fam-edge:oracle_db.py 新增 delete_video()(删 events+videos 行 + 磁盘上
的运动片段文件;不清理 ss_motion_events 源事件,因为素材一旦处理完就不会
被生产者重新捡起,删片段不会触发重新分割);api_gateway.py 新增
POST /api/oracle/video/delete
- fam-core:新增 db_layer.delete_sync_video()(第一个"NAS 直接写自己镜像表"
的函数——增量同步只做 upsert 感知不到删除,不能像纠错那样靠 trigger_now()
拉增量顺带清理)+ oracle_sync.push_video_delete() + ui_api.py 新增
DELETE /api/ui/videos/<id>(先回推 Oracle,成功后才清本地镜像,避免数据
不一致)
- fam-ui:Timeline.vue 详情卡片加删除按钮(原生 confirm() 二次确认,项目里
之前没有确认弹窗组件先例);api.js 新增 deleteVideo()
新增 6 个 delete_video 单元测试;已在 Oracle/NAS 用自造测试数据完整跑通端
到端链路(转发成功、两端记录清理、磁盘文件删除、幂等 404),未触碰任何真
实监控数据。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com >
2026-08-24 04:25:43 +08:00
ericwyuan
9bba7b7e14
fix(chat): 流式问答中文乱码 + 问答模型链换成专用文字大模型
...
## 乱码修复
Gemini 流式接口 resp.iter_lines(decode_unicode=True) 没有显式设置
resp.encoding,requests 自己猜编码猜错了(Gemini 的 SSE 响应 Content-Type
不带 charset 参数),导致中文变成典型的 UTF-8 被当 Latin-1 解码的乱码。
fam-core 转发这一跳的 requests.iter_lines 调用有同样的坑,一并修了。
两处响应的 Content-Type 都补上 charset=utf-8,减少下游再犯同样错误的机会。
顺手用 latin-1/utf-8 往返把 NAS chat_history 里已经存了乱码的 3 条历史记录
恢复成正常文字。
## 问答模型链重新设计(跟视频分析完全独立)
用户要求问答不再用 flash/omni,优先用 NVIDIA 免费文字模型里上下文最大的几个,
不行再退 Gemini 非 flash 文字模型,最后本地 Ollama 兜底。
- base_adapter 新增 usage 字段(纯标记,不影响行为)
- qa.py 的 QAOrchestrator 只挑 role='text' 的适配器参与问答(按 config 出现
顺序决定降级顺序),role='vision' 的视频分析适配器(gemini-flash-latest /
nvidia omni)不再参与问答,两条链路彻底解耦
- 新增问答专用 NVIDIA 文字模型链(同一适配器内部 model_chain 降级):
nemotron-3-ultra-550b-a55b(1M上下文/561B) -> nemotron-3-super-120b-a12b
(1M上下文/124B) -> openai/gpt-oss-120b(131K上下文/117B)。三个都用真实
API 调用验证过当前账号可访问;同时验证过 llama-3.1-70b-instruct 和
nemotron-super-49b-v1.5 都收到"08/25/2026 起弃用"通知,排除;
nemotron-ultra-253b/mistral-large-2/nemotron-4-340b/kimi-k2.6 在目录里
能看到但实测调用返回 404"账号无权限",排除
- 新增问答专用 Gemini 非 flash 文字模型:gemini-pro-latest -> gemini-2.5-pro
(1M 上下文,复用视频分析同一批多 key 轮换)
- /api/edge/chat/ask(/stream) 默认 max_tokens 从 1024 提到 3072:新链路
优先用的都是"推理"模型,回答前会先吐一段思考过程,1024 经常在思考阶段
就被截断,用户永远看不到真正答案
新增 test_qa.py::test_init_only_keeps_text_role_adapters_in_config_order
验证角色过滤+顺序正确。
本地检查过 Ollama 服务本身:systemd enabled+running(8h 正常运行,
qwen2.5:7b 已加载),并非没启动——用户观察到的现象另有原因。
2026-08-23 07:46:48 +08:00
ericwyuan
2c5bf950c5
feat: 事件时间轴缩略帧 + 人物管理头像 + 人物合并硬规则校验
...
## 新架构:Oracle 集中计算 + NAS 代理展示
### Oracle 端 (fam-edge)
- 新增 frame_service: ffmpeg 视频抽帧 + VLM 人物定位裁剪头像(磁盘缓存)
- 新增 /api/oracle/frame: 按 video_id+ts 抽帧返回 jpeg(带 token)
- 新增 /api/oracle/avatar: 按 label 生成人物头像(VLM 定位人物 + 兜底整帧居中)
- 新增 person_identifier: 人物身份识别模块
- Gemini 适配器支持 flash/flash-lite 双模型切换,429 自动降级
- frame_service VLM 全模型 429 时进入 10 分钟熔断,避免每次请求白打配额
- 兜底头像不落缓存,配额恢复后自动重试 VLM 精确定位
### 人物合并硬规则校验(框架级修复)
- person_service: LLM 合并结果落库前加硬冲突检测
- 性别冲突 → 绝不合并
- 年龄档跨未成年/成年 → 绝不合并(防止把爷爷/宝宝并进同一人)
- oracle_db: upsert_person 入口剥离括号后缀(人物A(别名:人物B) → 人物A),消灭垃圾人物行
- 修复 set_canonical 丢弃 source 参数的 bug(旧代码硬编码 'manual' 导致错误合并被永久固化)
- get_events_for_label: 只提取该身份组的特征文本,头像定位更精准
### NAS 端 (fam-core)
- 新增 img_proxy: /api/proxy/frame 和 /api/proxy/avatar 代理 Oracle 图片
- app.py 注册 img_bp 蓝图
- oracle_sync / db_layer / member_manager 同步人物表
### UI 端 (fam-ui)
- 事件时间轴: 每条事件卡片加时间点缩略帧
- 人物管理: 每人卡片加头像(150x150 圆角)
- parse_persons: 剥离括号备注,与 Oracle 归一化一致
- 新增 EventItem 组件、Timeline 页改造
- Chat / ServiceStatus 页相应调整
### 数据库
- scripts/ddl.sql: 同步表结构更新
- Oracle people 表: features_json / display_uid / source 字段完善
2026-08-23 00:13:56 +08:00
ericwyuan
1dd9df34c2
[功能] 事件-片段一致性对账 - /api/oracle/activity 加 segment.consistency(事件总数/已结束/已分割/缺口+明细/素材覆盖范围), 服务状态页展示一致性数字
2026-08-22 13:40:42 +08:00
ericwyuan
cbcc5d0325
[功能] 服务状态页新增「视频分割」状态 - Oracle /api/oracle/activity 加 segment(运动片段 done/pending/failed/文件数/最近分割活动), ServiceStatus.vue 加视频分割卡片
2026-08-22 13:18:57 +08:00
ericwyuan
2d9d96de56
[功能] fam-edge 运动心跳支持 - /api/ss/motion 空 events 心跳刷新, has_motion_in_range_local 按 max_heartbeat_age_sec(900s) fail-open
2026-08-22 12:08:00 +08:00
ericwyuan
38dea6ba2e
[重构] 运动预过滤改为本地事件(NAS推送) - 甲骨文不再反向访问NAS:新增 ss_motion_events 表/record_motion_events/has_motion_in_range_local,/api/ss/motion 接收端点,video_processor 改用本地运动事件预过滤,停用 dsm_motion_client 反向查询
2026-08-22 10:25:56 +08:00
ericwyuan
61db82cb9b
refactor(fam-edge): 重构第一阶段 - 人物图片零额外调用 + 运行时稳定性 + 工程质量
...
人物图片功能重做: bbox 随核心视频分析那一次 Gemini 调用一并产出(prompts.py 加
person_appearances.bbox 字段, [ymin,xmin,ymax,xmax] 0-1000 归一化), frame_service
直接用存好的 bbox 裁剪头像/事件缩略图, 删除原来"展示时额外调用 Gemini 定位人物"的
整套逻辑(locate_person_bbox/VLM 校验/熔断), 从架构上消除与核心视频分析共抢配额的
问题; 用真实数据验证裁剪结果正确框住人物本体。
NVIDIA 模型修复: 实测原配置的 3 个模型均不可用(asset_id 引用 500/400, 不支持视频),
改用 nemotron-3-nano-omni 的 base64 内嵌视频方式(唯一实测打通), 加 max_base64_mb
防止对大文件做注定失败的编码。
Gemini 多 Key 轮换: 支持 extra_api_keys 配置多个独立项目的 key, 配额用尽时依次
换 key 重试(每换 key 需重新上传, Files API 按项目隔离)。
稳定性加固: CircuitBreaker HALF_OPEN 清空旧失败计数(修复探测一失败就重新 OPEN 的
bug); chat() 统一接入熔断器(原来只有视频分析路径检查); NVIDIA 适配器改用共享
json_parser(原来自己重复实现且不做 schema 校验); Gemini Files API 上传超时也尝试
清理远程孤儿文件; video_processor/video_queue 里直接操作 OracleDB._conn 的裸 SQL
改走新增的 set_event_start_time/mark_video_invalid/reset_video_to_pending 方法;
/health 加入队列线程存活状态; 密钥改用 ${ENV_VAR} 引用(.env 已支持自动加载),
不再明文写入 config.yaml。
工程质量: 新增 fam-edge/tests(32 个单元测试, 覆盖熔断器状态机/JSON 解析容错/
时间戳解析/bbox 坐标换算/多 key 解析), 新增 scripts/smoke_test.py(发版前接口
稳定性检查); 清理死代码(OllamaAdapter.analyze_frames、get_sync_delta 死分支、
未使用的 vision_timeout/max_concurrent_tasks 配置项); 修正 get_events_for_label
排序(改最近优先 + 过滤畸形历史时间戳)。
已部署 Oracle 并跑通 smoke test 全部 6 项检查。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com >
2026-08-22 00:22:57 +08:00
ericwyuan
ff01d14c79
feat(v3): 人物模块重构 - 大模型结构化特征值替代 OpenCV 帧定位。prompt 增加 person_appearances(uid+7特征+action)并重写合并 prompt(稳定特征优先比对);gemini 透传特征字段;oracle_db events/people 加 person_appearances_json/features_json/display_uid + _merge_features;video_processor 去 cv2 改 ffprobe 校验、_store_result 聚合 uid 特征落 people、删缩略图/事件截图;person_service 聚合特征后基于特征文本 LLM 合并;api_gateway 删 3 个图接口;NAS 镜像+DDL 加新字段;fam-ui 特征卡替代头像、事件时间线人物特征块
2026-08-21 18:06:21 +08:00
ericwyuan
2ba3478291
fix(头像): 人物头像改为'出现事件画面'而非视频首帧 - 新增 /api/oracle/person/avatar(在该人物所有出现事件中返回第一个有截图的事件画面,JSON 精确匹配);撤销 event_thumb 的视频首帧兜底(首帧可能无人/非本人);fam-ui 人物页改调 avatar 接口
2026-08-21 16:13:05 +08:00
ericwyuan
27bfd6abbe
fix(api): 事件截图接口兜底 - ev_{id}.jpg 缺失时返回所属视频首帧缩略图(thumbs/{video_id}.jpg),避免旧事件/未生成截图导致前端头像空白
2026-08-21 16:09:55 +08:00
ericwyuan
0d0a7f6ef8
feat(监控): 实时服务状态界面 - Oracle 新增 service_activity 活动表(只保留7天,写入时清理)与 /api/oracle/activity 接口(队列实时状态/当前处理视频/模型调用/rclone/人物合并/最近50条活动);VideoQueue 打点+当前处理跟踪+status();PersonService 打点;rclone_sync.sh 同步结果写活动表;fam-ui 新增'🖥 服务状态'页(状态卡+活动时间流,直连 Oracle 实时拉取)
2026-08-21 15:52:36 +08:00
ericwyuan
05f727a9ef
feat(events): 每个事件生成对应时间点画面截图 - oracle_db.mark_video_processed 返回 event_ids;video_processor 按事件时间戳-视频起始时间偏移跳帧截图存 ev_{id}.jpg;新增 /api/oracle/event/{id}/thumb 接口;fam-ui 事件列表每条显示对应截图
2026-08-21 13:44:16 +08:00
ericwyuan
41485072bf
fix(ui): sync_events 查询补 JOIN sync_videos 取 camera_name(修 1054 报错);feat(缩略图): Oracle 处理成功后抽首帧存 thumbs/{id}.jpg + /api/oracle/video/{id}/thumb 带 token 接口;fam-ui 视频会话显示缩略图(oracle_url+token);start_ui.sh 注入 .env
2026-08-21 13:37:23 +08:00
ericwyuan
9b1cc8f93b
[阶段2] FAM-Edge 重构为整视频分析+同步接口+人物服务 - 移除切片/抽帧/队列,新增 oracle_db/person_service/qa/watch_processor/video_processor,api_gateway 提供 /api/oracle/sync 与 /api/oracle/people/correct
2026-08-21 10:38:15 +08:00
ericwyuan
43b3178a9d
feat(人物管理/红框标记): 关键帧人脸标记 + 人物命名重命名回溯 + 人物管理页改版
...
1. fam-edge 新增 frame_marker.py: Edge 端人脸检测画红框+统计人脸数(NAS ARM 太弱只存图零计算),orchestrator 分析后回传前标记,新增 /api/edge/mark_frames 批量补标端点
2. fam-core 新增 tools/backfill_mark_frames.py: 存量关键帧批量补红框(幂等+备份 frames_orig)
3. db_layer.name_member 增强: 支持自动注册新标签/重命名回溯(旧真名一并替换)/多人组合字符串 REPLACE
4. fam-ui 成员命名页改为人物管理页: 所有人物照片墙+命名重命名+统计去重
5. event_receiver/member_manager 配套适配
2026-08-21 00:53:30 +08:00
ericwyuan
8cb5553beb
fix: Edge端三处修复 — API key丢失致全模型失败 + 失败结果黑洞 + 重派发死锁
...
根因: Edge服务手动重启未source .env,GEMINI/NVIDIA key丢失,
所有视觉模型调用失败("All models failed"),且失败结果永不回传NAS。
1. config_loader: 启动时自动加载.env(export KEY=VALUE),已存在环境变量不覆盖
2. queue_manager.enqueue: FAILED(或已交付SUCCESS)行重置为PENDING复用重跑,
修复 INSERT OR IGNORE + UNIQUE(nas_task_id) 导致的静默忽略死锁
3. get_undelivered_results: 包含FAILED状态,/results构造failed负载,
NAS Poller可感知失败避免僵尸循环空转重传
4. ddl.sql: failure_stage ENUM 增加 'process'(与db_layer valid_stages对齐),
修复 pymysql DataError 1265
验证: task 293(压缩后22MB) NVIDIA降级分析成功→event_id=13;
task 295 全链路成功→event_id=14;NAS端ENUM已同步ALTER
2026-08-20 15:35:40 +08:00
ericwyuan
203b076783
feat: NAS端FFmpeg预压缩 — 根治360MB视频跨境上传超时
...
根因诊断(网络问题,非代码逻辑):
- NAS(中国移动) → Oracle(美国Phoenix) 跨境上行带宽实测仅 0.5-0.9MB/s 且波动大
- 下载方向 3.0MB/s(非对称,典型国际出口拥塞)
- Tailscale P2P: ping 可通(224ms)但持续数据流被阻断(0 B/s, 127s超时)
- 360MB 原始视频上传需 10-20min,任何大分块都会超时
NAS dispatcher:
- 新增 _compress_video: 480p/CRF28/veryfast,静态监控场景实测 ~36x 压缩比
(360MB → ~12MB,上传时间 20min → ~30s)
- ffmpeg 自动探测: CodecPack ffmpeg41(带libx264) > /usr/local/bin > PATH
(Synology 系统 ffmpeg 被裁剪,无 h264 编解码)
- scale 两级滤镜: force_original_aspect_ratio=decrease + trunc(iw/2)*2
(h264 要求偶数尺寸,853x480 会报错)
- 压缩缓存 /tmp/fam_compressed/task_{id}/,源文件未变则重试复用,TTL 24h
- 压缩完成后重置 PROCESSING 状态(重置 stale 回收计时基准)
- 压缩失败回退原始文件分块上传
- _query_uploaded_chunks 失败时记录日志(原静默失败导致全量重传无感知)
Edge api_gateway:
- fix: total_chunks 变更清理旧分块后同步重写 meta.json
(原 bug: meta.json 不更新导致每块上传都触发清理,删除同批新分块死循环)
config:
- stale_timeout 600→1800(覆盖压缩+上传+Edge队列积压总时长)
- 新增 ffmpeg_path / compress_timeout 配置项
2026-08-20 14:59:50 +08:00
ericwyuan
5915cf4be3
fix: 分块大小减至5MB + Edge端chunk_size变更自动清理 + 断点续传防护
...
NAS dispatcher:
- CHUNK_SIZE 10MB→5MB(~1Mbps上行带宽下可靠传输)
- chunk上传timeout (30,120)→(60,180)(增加连接和读取余量)
- _query_uploaded_chunks 返回 (set, edge_total) 元组
- expected_total != edge_total 时跳过断点续传(防止chunk_size变更导致文件损坏)
Edge api_gateway:
- upload_chunk 检测 total_chunks 变更,自动清理旧分块
- 防止不同chunk_size的旧分块与新分块混合导致assemble后文件损坏
2026-08-20 14:18:29 +08:00
ericwyuan
b6c13a9047
feat: 分块断点续传上传 — 20MB/块 + 分块级重试 + 断点查询
...
Edge 端新增 3 个端点:
- POST /api/edge/video/chunk: 接收单块,保存到 task_{id}/chunk_{index:04d}
- GET /api/edge/video/chunks: 查询已上传分块(断点续传)
- POST /api/edge/video/assemble: 合并全部分块入队
NAS Dispatcher 重写:
- 大文件(>50MB)自动分块上传(20MB/块)
- 每块最多重试 3 次(分块级重试,非整文件级)
- 上传前查询已上传分块,跳过已有的(断点续传)
- 全部上传后调 /assemble 合并入队
- 小文件(<=50MB)走直接上传路径
- max_retries 3→5(文件级重试次数)
- scheduler 切回生产目录
解决: 360MB 视频跨公网单次上传超时/断连问题
2026-08-20 12:27:30 +08:00
ericwyuan
02da23ef42
feat: 异步任务队列架构 - SQLite队列 + 速率限制 + NAS Poller
...
Edge端:
- 新增 SQLite 异步任务队列 (queue_manager + consumer)
- 新增 TokenBucket 速率限制器 (Gemini 1000 RPM, NVIDIA 40 RPM, burst 2x)
- 新增 /api/edge/video/enqueue + /api/edge/results 端点
- 消费者线程从队列消费任务,按速率限制调用AI模型
- orchestrator 集成 rate_limiter,Gemini优先→NVIDIA兜底
NAS端:
- Dispatcher 重构为 enqueue 模式(上传后立即返回,不等结果)
- 新增 Poller 线程(定期从Edge拉取结果写 MariaDB)
- app.py 启动 Poller,config.yaml 新增 poller 配置
- db_layer 更新 valid_stages 添加 'process'
2026-08-20 12:07:09 +08:00
ericwyuan
babf5b09a9
[架构重构] 移除本地Ollama融合,云端直出JSON直存DB,Q&A三模型降级
...
1. 视频摘要链路:云端VLM直出结构化JSON → Edge format_cloud_result格式化校验 → 直存NAS DB(移除run_text_fusion本地融合)
2. 智能问答链路:Gemini→NVIDIA→Ollama降级,新增chat()纯文本问答方法
3. 适配器重构:base/gemini/nvidia/ollama adapter新增chat();gemini多图单请求结构化JSON;nvidia逐帧调用聚合
4. 端点变更:/api/edge/chat → /api/edge/chat/ask,调orchestrator.run_qa()
5. chat_handler改经Edge Q&A编排,不再直连Ollama
6. 配置更新:ollama_url → qa_url,Ollama role注释改为Q&A兜底
7. README同步更新架构描述、拓扑图、时序图、模块表
2026-08-20 10:21:09 +08:00
ericwyuan
40944428d1
feat: video analysis switched to push mode (upload whole video, sync response)
...
Rationale: Oracle cannot reach NAS (Tailscale userspace mode on NAS, no TUN),
the old pull+webhook design requires Edge to download video from NAS and
callback to NAS - both blocked. New design is one-way NAS -> Oracle:
- FAM-Edge: new POST /api/edge/video/push endpoint accepts multipart video
upload, reuses existing OpenCV scene-change keyframe selection, analyzes
synchronously and returns the result payload directly in the HTTP response
(no webhook callback). Old /api/edge/video/analyze kept for compatibility.
- FAM-Edge: VideoPreprocessor.save_upload() saves the uploaded file
- FAM-Edge: AIOrchestrator.process_push_task() runs the full pipeline
(health check -> extract -> select -> compress -> VLM -> fusion) and
returns callback-style payload dict
- FAM-Core: Dispatcher rewritten to push mode - reads local video file,
uploads with task metadata (camera_name, event_start_time from file mtime,
known_members_context), applies the result to DB via shared
event_receiver.apply_success_event()
- FAM-Core: event_receiver success logic extracted into reusable
apply_success_event() (used by both webhook route and dispatcher)
- config: edge_url -> /api/edge/video/push, push_timeout 1800s, gunicorn
Edge timeout raised to 1800s for long synchronous analysis
2026-08-20 01:03:48 +08:00
ericwyuan
94a8805045
feat: FAM-Edge chat proxy + PyMySQL migration for FAM-UI + config update
...
- FAM-Edge: add /api/edge/chat proxy endpoint forwarding to local Ollama
(Ollama port 11434 not exposed externally, FAM-Edge acts as reverse proxy)
- FAM-Core config: edge_url and ollama_url switched from Tailscale IP to
Oracle public IP (Tailscale firewall blocking between NAS and Oracle)
- FAM-UI: migrate mysql.connector to PyMySQL (same as FAM-Core)
- FAM-UI: cursor(dictionary=True) replaced with cursorclass=DictCursor
- End-to-end chat verified: FAM-Core -> FAM-Edge proxy -> Ollama -> response
Answer: 今天没有观察到张三 (no events in DB yet, expected)
2026-08-19 23:59:28 +08:00
ericwyuan
cdd1f21d4c
[3.1-3.5] FAM-Edge 全链路 - API-Gateway/Video-Preprocessor/AI-Orchestrator/模型适配器(基类+Ollama+Gemini)/熔断器/JSON解析容错 + 配置
2026-08-19 22:25:38 +08:00