Branch:
main
along
along-test
main
memory-center-impl-v1
memory-design-v0.2
${ noResults }
13 Commits (main)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
1f9925773d |
清理:第 3 批,Flutter 业务代码孤儿文件、死路由、无人用的服务与依赖(约 7500 行 + 24MB 素材)
- 23 个从 main.dart 不可达的孤儿文件(火山 ASR/TTS/声音复刻一组 3635 行、goodsai 页、
siri_orb/global_ai_orb、intro_overlay_builder、杰理 OTA 残留弹窗与 model 等)
- lib/modules/voice_replication 整目录:Routes.voiceReplication 唯一引用在注释里
- 注册但无人 find 的服务:TranslationUsage / MicrosoftTranslation / Navigation / Location
- api.dart 三个服务端已无接口的方法:getAliToken / getMusicUrl / getproducts
- pub 依赖:tencentcloud_cos_sdk_plugin、flutter_tts、flutter_shaders、flutter_svg、synchronized、
markdown(flutter_markdown 仍会传递依赖)、pip;shaders/ 随 flutter_shaders 一起去掉
- 素材:assets/images/agents(19MB)、assets/weather(2MB)、explore*、defaultPicture*、
robot_light/glasses_light/qqmusic/music、两个 lottie、listening.mp3
- dart analyze 报的 unused_import 31 处、unused_element 17 个(含连带暴露的 4 个)、
unused_field 8 个、unused_local_variable 8 处,全部清零
⚠️ pubspec.lock:仓库里这份锁与 pubspec 早已对不上(改动前的 pubspec 跑 pub get 也会重解析 82 行),
本次重解析后 firebase_auth 6.7.0 拉进的 firebase-auth 24.2.0 要求 Kotlin 2.3、工程是 2.1.10,
Android 编不过;故把 firebase_core / firebase_auth 钉回 4.13.0 / 6.5.7(pubspec 里有说明)。
其余 36 项均为小版本升级。
验证:dart analyze 无 error 且 unused_* 为 0;flutter test 221 全过;
flutter build apk --debug 与 flutter build ios --no-codesign 均通过。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 355632a5b41818d33ff9c36b08440777d955cd62)
|
2 weeks ago |
|
|
c12651e79c |
语音纪要:录音语言自动识别、转写不翻译、翻译只作用于总结;可选总结模型;详情加 PPT 页签(仅界面)
客户端
- 首页标题「语音记录助手」→「语音纪要」,去掉顶栏右侧的算力摘要
- 「生成」弹窗:去掉录音语言(传空,交给识别服务自动判断);「目标语言」改为「翻译」,
默认「默认」= 不翻译、总结跟随录音语言,选了语言 = 总结/概览用该语言输出;
新增「总结模型」一行(echomeet_getsummaryservices 拉列表,记住上次选择,summary_svc_id 带进 starttask/summary)
- 重新总结选「默认」时传 tolanguage=auto(该接口空串历来表示沿用上次语言)
- 纪要详情在思维导图右边加「PPT」页签:示意图 + 「生成 PPT」按钮,点了提示即将上线
服务端(echomeet)
- 转写不再翻译:TranslateProcess 只做 Translate=Original;StartTask 不再选翻译服务
- 总结输出语言由 summaryLanguageInstruction 追加在模板 system prompt 末尾(空=跟随转写语言)
- 模板解析 ResolveSummaryTemplate:有目标语言先找该语种版本,否则用客户端选中的那条
- formlanguage / tolanguage 的 "auto" 与空串同义;Azure 批量转写不指定语言时带候选语种自动识别,被拒退回 en-US
- console「会议配置 → 会议总结」:默认模型之外可勾选「供用户选择的模型」,勾了即打开应用级「允许客户端选择」
⚠️ 行为变化:服务端部署后老版本 App 也不再翻译转写(原「英文录音→中文」现在是英文转写 + 中文总结)。
验证:dart analyze 无 error;flutter test 224 全过;go build/vet 通过,echomeet/console 测试通过;
admin npm run build 通过。已部署测试服(app/console/admin),release 包装到 ALN-AL80。
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
2 weeks ago |
|
|
9f105bf7c8 |
会议纪要分享:链接最后一段改成服务端签发的只读票据,不再放登录 JWT
链接格式 share.ymaikj.com/share/{serverHost}/ymaikj.com/{id}/{ticket}。最后一段
以前放的是 logintoken(完整登录 JWT)——谁拿到链接就拿到了整个账号,转发进群、
被截图、进浏览器历史都等于把账号交出去,而且除了改密码没有任何失效手段。
现在是 HMAC 签名的分享票据:绑定单条记录、只读、默认 7 天过期,密钥由
FIELD_ENCRYPT_KEY 派生(上线不需要新配置项)。
- echomeet_getsharedrecord 必须在 gateway 白名单里——打开链接的人本来就没登录,
漏了是 code:18、表现为「分享链接打开一片空白」。签发端 echomeet_createsharelink
反之,不要加进白名单。
- 落地页嵌在 gateway 二进制里(go:embed),路由 GET /share/*any 必须是 catch-all:
路径里的段是数据不是文件路径,gin.Static 会去磁盘找同名文件、一律 404。
- 落地页的 ALLOWED_HOSTS 白名单不能删:serverHost 来自 URL,不校验就等于「构造
一条链接让别人的浏览器带着票据去请求任意域名」。新增环境要加一行。
- 极简 Markdown 渲染器先 esc() 再套正则,块级语法要匹配转义后的形态(引用块匹配
的是 > 不是 >),写成 > 不报错、只会静默退化成普通段落。
客户端侧:分享前先申请票据并加载提示,拿不到就不分享出一条必然打不开的链接。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
|
|
f9b9c8c72d |
client: 翻译默认归档进语音纪要;列表改本地优先;登录按包收敛;EMAI 走 A2DP
## 通话/同声/面对面翻译 → 语音纪要 三种翻译模式默认边翻边录,结束时把音频**连同已有转写**一起归档 (rtype=VOICETRANSLAT),服务端跳过识别、直接用智能模板(General-Autopilot)总结, 在会议纪要里就能看到。转写段的构造见 translation_export.dart(纯函数,带金测试): - 时间是**相对录音起点的毫秒**并扣掉暂停区间——面对面是逐句按住说话、松开就暂停的, 不扣的话越往后偏得越多;写成绝对时间戳会让点一句跳到音频末尾之外; - 说话人用 Speaker_0/Speaker_1,与服务端转写同一口径(转写页直接把这个字段当文案显示, 裸 0/1 会和别的纪要长得不一样,喂给 LLM 抽待办时也会混出 Speaker2 这种负责人); - AST socket 断开后 _terminateCallOnError 会取消事件订阅,重开时收不到任何转写 (真机上表现为「有日志但转写 0 段」),改成重新订阅。 ## 语音纪要列表:服务端是真源、本地 sqlite 是缓存 列表接口改回骨架字段后(服务端同批),客户端改成本地优先渲染 + 后台对齐: - Synchrodata 重写:拉骨架 → upsert → 删掉「本地有服务端没有」的记录(连本地音频、 上传队列、轮询任务一起清)。**业务错误时 data 是 null 不是空列表**,判空错了 会把本地整个清空; - 详情进页面时才拉整条;离线操作队列那套上行是死代码(服务端接口整个注释掉了),删除。 ## 登录按包收敛 苹果包只留苹果+游客、安卓包只留微信+游客、谷歌包只留邮箱+游客。 判定抽成 resolveLoginMethods 纯函数(login_methods.dart + 8 条测试), 邮箱包还要锁死国家区号选择。 ## EMAI 与其它 - 手机侧触发的对话走 A2DP 而不是语音处理通道:iOS 上 A2DP 输出与 voiceChat 模式 互斥,开了后者蓝牙耳机会当场切到手机扬声器。按当前实际输出路由决定要不要开 voiceProcessing/回声消除; - EMAI「开始对话」闪退:探测输入格式时 new 了一个一次性 AVAudioEngine 去读 inputNode, 配置变更时崩在 AVAudioIONodeImpl(EXC_BAD_ACCESS)。改成直接问 AVAudioSession; - 语音纪要文件名可改:每行右侧加铅笔图标,复用已有的重命名弹窗(原来只有长按菜单, 实际入口已不可达); - 拾忆上报 IANA 时区名:DateTime.now().timeZoneName 给的是「CST」这种缩写, 服务端 LoadLocation 解析不了、静默退回容器时区,海外用户的提醒时刻会全按北京时间算。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
3 weeks ago |
|
|
41e3a023a3 |
client: 蓝牙名白名单(EaiRec/EaiCar);连接即按账号登记绑定;iOS 崩溃加固;弱网翻译超时
- 设备名闸门:支架只认 EaiRec/EAIMAR/Pad Note,车载香薰只认 EaiCar,固件名对不上直接不连; 一次性清掉此前被归一化名字污染的配对记录;Smartcar 资源与 agent 命名统一改为 EaiCar - 绑定登记:新增 DeviceBindRegistry(按 uid|MAC 去重),支架也进 verifyConnected, 登录态从无到有时补报一次,换账号不再漏切绑定 - iOS 崩溃加固:补 NSPhotoLibraryAddUsageDescription(保存思维导图/统计图必崩); StsAgent 先配音频会话再 installTap 并校验格式;支架录音两处 installTap 加 0Hz 守卫; AzureTtsHelper 播放队列改加锁队列(三线程裸改 Array,release 下崩); MicrophoneCapture 主线程不再用信号量等权限弹窗(看门狗误杀) - 网络:connectionError 给中文文案;实时翻译请求 12s 超时,不再跟 30s 默认值等 - 附:AI 助手中枢设计文档 v0.2(未动代码) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
4 weeks ago |
|
|
e00b617025 |
services,admin,client: 服务配置按应用隔离、应用参数下线、客户端凭据走加密 v3
后台 - 服务配置去掉「全局默认」层,svc_config / svc_region_override / echomeet_orch / echomeet_orch_setting / echomeet_template 五张表统一按 app_name 隔离, 启动迁移 migrate_scope.go 幂等分配(首个应用原地改名保 id,其余拷贝); 写接口 app_name 必填(scope_check.go)。 - 类别落库 svc_category,可增删改;内置 11 个 id 不可改不可删,运行时语义仍只挂 comm.SvcCat*。 - 「应用参数」标签页下线:业务库 config 表不再随 user_getappconfig 下发, migrate_appparams.go 把 AGENT_TYPE / MOBILE_ELF_* 迁到服务 llm_mobile_elf、 iapCustomerServiceQQ 迁到 app_params,COMPUTE_*/NEWUSER_GIFT_* 留在 config 表不下发, 无人读的音乐/公码/导航/COS_*/MeetServers 删除,其余键保留并在启动日志告警。 - 删除 console appcfg/* 与 api 模块的 get/add/update/delconfig、唤醒音四个接口(无调用方)。 - 修 getmcpservers 仍查全局层导致 MCP 类服务永远读不到。 客户端 - 凭据改走结构化通道:user_getappconfig_v3 整体 AES-CBC 加密(api_crypto.dart, 拦截器须排在 AuthInterceptor 之前),AppConfig.cred(svcId, field, envKey) 取值, 老键名只作兜底;lib 内 AppConfig.env( 已降到 0 处调用。 - 图片翻译的 ALIBABA_VL_MODEL / ENDPOINT 改挂 ast_alibaba。 启动日志 - 删除 sys/auth/firebase(google_auth 的重复实现,零调用方,且同一错误刷两行)。 - api 模块不再每次启动无条件插入默认超管(Duplicate entry 'admin')。 - sys/auth/* 五个包的 logger 名从复制来的 sys.tavily 各改各名; google_auth 启动时即判断配置是否可用。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
27cabe1bde |
client: 设备内核抽象、Smartcar 车载香薰接入与语音纪要等一批改动
工作区里累积的 Flutter 客户端改动,随本轮一并入库。主要涉及: - lib/devices/ 设备内核:DeviceHub / DevicePlugin / DeviceSession 抽象, 恒玄(bes)、EaiRec 支架(holder)、Smartcar 车载香薰(smartcar) 各自的插件实现, 业务层不再直接依赖任何厂商类;分层边界由 test/devices/ 下的测试守着 - local_plugins/device_plugin_interface、device_smartcar、recorder_holder: 纯 Dart 契约与原生侧实现 - lib/modules/ 能力模块注册表、语音纪要、OTA 升级等模块 - test/ 新增设备协议、Ogg Opus 时长、模块注册表、路由等金测试 - ios/sync_spm_platform.sh:修 Xcode 直接构建时 SPM 平台版本被打回 13.0 的问题 验证:dart analyze lib 无 error(264 条 warning 均为 HEAD 上既有的 unused_import / avoid_print 等 lint,非本次引入)。未跑真机与打包。 注:这批不是本次会话中由 Claude 编写的,此处仅做入库。 |
4 weeks ago |
|
|
714a05b6d4 |
修 iOS EMAI 没有语音播报、连续对话上行断供;接入 MCP 令牌
iOS 上助手一直不出声,和这次改连续对话无关,是播报本来就坏的。三轮真机对照 实测:just_audio 喂的是「长度未知的直播 WAV 流」,AVPlayer 只发一次 `Range: bytes=0-1` 的嗅探请求就判定资源不可播,回 -11850 且 processingState 直接落 idle,一次都没真正起播。声明长度、支持 range、把收到的字节全缓存下来 允许重放,三种都试过,结论一样。ExoPlayer 不发这个嗅探请求,所以安卓一直是 好的——这就是「安卓有声、苹果没声」的全部原因。 iOS 改走原生 AVAudioEngine 排队播 PCM(PcmSpeakerPlayer),安卓不动。 验证方式:一次性灌 3 秒音频,7ms 喂完但 3708ms 才播完,证明是实时渲染不是丢弃。 连续对话的上行也是断的,服务端一直回 ClientAudioTimeout。两个原因: - _ensureContinuousMic 被 Started 事件和 _enterContinuous 并发调用,两边都在 `_micRunning` 置位前通过守卫,同一个 recorder 被 startStream 两遍,第二次把 第一次那条流顶掉。控制器侧改成同步置位,MicPcmStreamer 内部再加一层 in-flight 去重。 - 开麦前没配好并激活音频会话。会话停在 soloAmbient 且未激活时,startStream 会**成功返回**、引擎也起得来,但输入 tap 一个 buffer 都不回调,客户端毫无报错。 麦克风和播放器统一到 EmaiAudioSession 一份会话配置:各配各的会在连续对话下 互相覆盖。iOS 固定 voiceChat——record 的 echoCancel 会打开 voice-processing 并 把 mode 改成 voiceChat 持有着,播放这边写回 default 就是两边互相掀桌子。 androidAudioAttributes 仍是 media,别改成 voiceCommunication(华为会静音)。 连续对话交互按要求改:进页面只建会话不开麦,短按开始、再按结束、可反复, 退出即停,10 秒无对话往来自动结束。计时依据必须是对话事件不是音频包—— 麦克风没人说话也在稳定出包,拿它续命这个超时永远不会触发。 另修 _enterContinuous 在 reconnect() 一返回就判 `!= ready`:ready 要等服务端回 Started(一次网络往返),几乎每次都命中,于是刚打开的连续对话被当场关掉, 而 Started 稍后到达又会把麦开起来,留下一个界面上看不见的常开麦克风。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
1 month ago |
|
|
46c815eb87 |
修拾忆页第一次打开就报错:解包口径写错 + 红条抑制漏了 memory_list
两个都是我上一轮引入的,表现叠在一起就是「第一次打开拾忆界面抛异常」。
## 1) MemoryService 的解包口径写错(严重,后端一部署就全线失效)
DioManager.request 结尾是 `return response.data;` —— 返回的是 **data 本身**,
不是 Response。全仓既有调用方都直接下标取字段(my_devices 的 response['devices']、
bes_device_auth 的 resp['device'])。
而 MemoryService._data 写的是 `resp.data`。因为 request 的返回类型是 dynamic,
**编译与 dart analyze 都不报错**;后端还没部署时返回 null,正好走空分支,
所以本地一路绿灯。一旦服务端真的返回成功,每一次 memory 调用都会抛
NoSuchMethodError —— 也就是说这个功能在部署之后才会开始全面失效。
已改成 `if (resp is Map) return Map<String, dynamic>.from(resp);`,
并加 test/memory_service_unwrap_test.dart 把四种形状(成功有数据 / 成功空 {} /
业务错误 null / 传进来个对象)都钉住,任何人改回 .data 都会红。
## 2) 红条抑制名单漏了 memory_list
上一轮只加了 memory_today / memory_upcoming,**漏了 memory_list** —— 而它正是
打开拾忆页第一个调的接口(TodoTab.initState → refreshFromServer)。
后端没部署时网关返回 NoFindServiceHandleFunc,AuthInterceptor 就把
"[Handle Http] no found handle memory_list" 原样弹给用户。
改成按 `/api/home/memory_` 前缀整条抑制,读写两种情况都成立:
读在打开页面与切前台时自动调,页面自己有行内空态与「显示的是本地缓存」提示;
写的调用点各自弹了本地化的 memSaveFailed / memDeleteFailed / memCompleteFailed,
不挡的话会弹两条。逐个列举正是漏掉 memory_list 的原因,改前缀。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
1 month ago |
|
|
66463e53f6 |
记忆中心客户端:拾忆页接服务端 + 写操作 UI + 本地提醒 + 每日播报
dart analyze 零错误,flutter build apk --debug 通过。 零期修缮 - tabTodo 补齐 41 个语言。此前只有 11 个文件有这个 key,其中 8 个的值是 'Remin'(不是任何语言里的词,像 Reminisce 打了一半),缺 key 的 30 个经 GetX 回退到 en_US 同样显示 'Remin' —— 41 个语言里 38 个是坏的,只是不报错。 - i18n_audit.py 补「缺失 key」检查。它此前**结构上无法发现缺失的 key**:循环遍历 的是该文件已有的 key,且对 baseline 里没有的直接 continue。这正是 tabTodo 在 30 个语言里悄悄丢掉而没人发现的原因。现以 zh_cn 为基准,实测报出 7243 处历史缺失 (40 个语言只有 1 个不缺)。加了 --fail-on-missing/--allow-missing 给 CI 卡「只增 不减」,默认不开——一上来就红会让人直接把检查关掉。 - pubspec 引入 timezone + flutter_timezone。zonedSchedule 只接受 tz.TZDateTime, 没有这两个依赖,提醒功能一行都写不了。 拾忆页从纯展示改成可写(此前最大的工作量遗漏) 页面原来没有 RefreshIndicator、没有 FloatingActionButton、卡片没有 onTap, 于是服务端的 add/update/del/complete 四个写接口在客户端一个调用点都没有, user_edited 那套「保护用户修改」的机制完全空转。本次补齐: - MemoryEditSheet:新增/编辑弹层,按分类分流表单(统一表存储 ≠ 统一表单) - 卡片 onTap 打开编辑、左侧勾选框标记完成(灵感不给勾选框,它没有完成一说) - 右下角 + 新增、RefreshIndicator 下拉刷新、带引导的空态页 - date_certain=false 的项单独归到「待定日期」小节。一次会抽出 8 条没写截止时间的 待办全堆在会议当天,不分开那一格就没法看了。 数据层 - MemoryItem / MemoryRemindSlot / MemoryReport 手写镜像服务端 pb。 **每个字段都经宽松解析器**(memInt/memBool/memStr),不直接 as int: 服务端删字段或老记录缺列时,non-nullable 会让整个 fromJson 抛 TypeError, 表现是整页空白而不是少一个字段(appconfig_model 已经为此踩过坑)。 单条解析失败只跳过那一条。 - 分类保留原始字符串而不是 enum 解析。服务端加分类只改常量不发包, 客户端 switch 不到就抛等于服务端一加分类就打崩所有存量 App。 CalendarEventCategory 加了 other 作为未知兜底,筛选与卡片分流都改成值比较 而不是穷举 switch。 - MemoryService 只缓存最近 90 天。GetStorage 是把整个 box 读进内存的 JSON, 一年三千多条全量缓存会让每次启动越来越慢。 - 拉取失败保留本地缓存并标 staleFromCache,页面显示行内「当前显示的是本地缓存」, 不清空、不弹红条。 - 勾选完成是乐观更新 + 失败回滚。不回滚会让用户以为已经勾上,下次进页面又变回来。 - memory_today / memory_upcoming 加进 AuthInterceptor 的红条抑制名单: 它们在启动与前台恢复时自动调,用户什么都没点就吃一条英文内部状态词很差。 删除 mock mockCalendarEvents() 与它引用的 44 个 demo* i18n key 一并删除(41 个文件共 484 行)。 真实数据上线后混着演示数据会让人以为系统出错。 本地提醒(MemoryReminderScheduler) - 通知 id 占 920000 起的固定区间,**逐条 cancel,绝不 cancelAll()** —— 仓库里已有 两个各自 new 的 FlutterLocalNotificationsPlugin 实例,cancelAll 会顺手干掉正在 录音的前台通知,表现是录音悄悄失去前台身份被系统回收。 - 单独的通知通道:录音提示是常驻低优先级通道,共用会让提醒被降级成静默。 - **只查权限不申请**。申请必须走 PermissionUtil(它收敛了过审要求的 48 小时冷却), 且只允许用户在权限管理页主动发起,这里主动申请等于绕过合规规则。 无权限时降级为「打开 App 才提醒」,不阻塞。 - 精确排期失败(Android 14 起 SCHEDULE_EXACT_ALARM 默认不授予)自动退化成 inexact 再试一次——不精确的提醒也比没有强,日志会记下来。 - 拿不到系统时区时按本机偏移找等价区兜底,不落回 UTC(那会让国内用户全部偏 8 小时)。 每日播报(MemoryDailySheet) - **不 await 在启动路径上**。冷启动已串四次网络往返,仓库刚做过相反方向的优化 (6a64c3d0 把会议模板拉取改成 unawaited)。这里是进主页后异步拉、拿到再弹。 - 排在隐私政策与强更弹窗之后,且必须在登录态确认之后。 - 通话翻译进行中一律不播报:两者共用同一个 AVAudioSession,在对方刚调好的会话上 再设一次会把通话音频路由弄乱(iOS 那条 phone_call 分支被改坏过三次)。 - 关窗必须真停:speakStream 是队列式播放,只 stop 不清队列会「关了还在念下一句」。 - 播报文案本地拼,不调 LLM。 生命周期与账号 - 切前台的刷新挂在 AppLifecycleManager(仓库已有 emai/translation/permissions 三个各自实现的 observer,本类就是收敛点),全部 unawaited。 - 登出清空记忆数据与已排提醒。不清的话换账号会看到上一个人的日程花销灵感, 提醒也会继续按上一个账号的安排响。 助手闹钟迁移 SET_clock 改为双写:本地 reminders 保留一个版本(上传失败时本机仍可用), 同时写服务端 memory_item(category=alarm)。**幂等键用指令 id** —— 本类的按 id 去重只在本进程内存里,换机/重装/重启后全失效,服务端靠 (uid, client_key) 唯一索引兜住。认不出的重复规则按单次处理,宁可只响一次也不按猜的规则天天响。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
1 month ago |
|
|
8d580a545d |
文件上传改走服务端签发预签名 URL,客户端直传阿里云 OSS
客户端不再持有对象存储的长期密钥。
旧实现用腾讯云 COS SDK 直连,bucket 与 SecretId/SecretKey 明文随
user_getappconfig 的 env 下发给每一个 App 用户(config 表的 COS_* 那几个键)。
抓包或反编译就能拿到这对长期密钥,而它对整个桶有读写权限——不只是自己那部分,
User/ 下所有人的录音都能被拉走或删掉。
现在:新增 user_getuploadurl,服务端签发 15 分钟有效、且只对某一个 object key
生效的阿里云 OSS 预签名 PUT URL,客户端拿着它直传。存储与 console 后台传固件/
产品图同一个桶(ymaioss),不再是两个云厂商两套存储。
服务端
- 新接口 modules/user/api_getuploadurl.go:
· object key 由服务端拼死 EAIMAR/User/<uid>/<scene>/<yyyy/MM/dd>/<12位随机>.<ext>,
uid 取自会话不采信客户端,客户端连自己传到谁的目录下都决定不了;
· scene 白名单 LocalAudio / TranslatAudio / ExternalAudio / ChatImages
——加新场景前要把客户端 addUpload 与 UploadOss.upload 的调用点全捞一遍,
少一个值就是那条链路整条报错,且只在真机跑到才发现;
· 扩展名白名单决定 Content-Type,不在表里直接拒,不回退成
application/octet-stream(那等于把桶变成任人上传的网盘);
· 随机名用 crypto/rand 而非 math/rand:key 可枚举就等于别人能猜到录音地址。
- 抽出 comm/ossconf.go:OSS 配置读取、上传根前缀、publicURL 拼接从 console 挪来,
console 改为调用它。两边必须落同一个桶,各写一份迟早漂移。
解密用 ${FIELD_ENCRYPT_KEY},console 与业务服务本就必须配同一把。
客户端
- upload_oss.dart 重写为「要 URL → dio 流式 PUT」,upload() 签名与返回值不变,
MeetingUploadService 不用改。用独立 Dio 实例:项目那个挂着 AuthInterceptor,
会加 token 与业务签名头、还会把响应按业务 JSON 解析,直传 OSS 时全是多余,
签名头还可能与预签名冲突。
- Content-Type 必须用服务端回带的值,它计入了签名,写别的会 403。
- agent 图片的 rootDir 从 'User/<uid>/ChatImages' 改成 'ChatImages':
前缀由服务端加,客户端再拼一层就是重复路径。
真机验证:录音落到
https://oss.ymaikj.com/EAIMAR/User/<uid>/LocalAudio/2026/09/04/howXMScPdsNP.wav,
下载 212524B 与库里 size 逐字节一致,Content-Type=audio/wav,文件头 RIFF/WAVE。
其余三个 scene 走同一段代码但尚未实跑。
⚠️ 新老客户端并存:老包仍读 COS_* 直传腾讯云,config 表里那 5 个键先别删;
历史录音的 audiourl 是 COS 绝对地址,不会自动搬家。
⚠️ 录音目前仍是公共可读的(bucket 公共读,实测匿名 GET 206、不存在的 key 404),
只靠 12 位随机 key 不可猜测来保护。要锁住得转私有桶 + 播放时也签临时读 URL,未做。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
1 month ago |
|
|
946204cc3c |
翻译延迟优化:接上阿里增量文本、面对面/同传去掉固定等待
通话翻译:阿里百炼端到端协议本来就是流式的(音频 response.audio.delta 一直在逐帧回灌),但增量文本订阅的事件名是错的——代码监听 response.audio_transcript.delta(字段 delta),而实时语音翻译 audio+text 模态实际发的是 response.audio_transcript.text(字段 text), .delta 是 OpenAI Realtime / 通义 Omni 的写法。于是 onPartialText 一次 都不触发,译文只在 .done 时整句蹦出来,看着像"等说完才翻"。 现在两套名字都认、两个字段都取,并按首次出现的事件名锁定,防止服务端 兼容层双发导致文本累计两遍。Android / iOS 两端同改。 面对面 / 同声翻译(走 ASR → HTTP 机器翻译 → TTS 三段串行): - 补上 Speech_SegmentationSilenceTimeoutMs=300,原来一行没设走 Azure 默认 500ms,这是"说完到出译文"里唯一一段纯等待; - 同传 / 音视频改单语 ASR,关掉连续语种识别(它要缓冲够音频、给候选 语言逐个打分才敢出 final)。只有面对面需要判断"谁在说",仍保持双语; - 中间结果翻译从"每 300ms 无条件发"改成 700ms + 至少多 4 个字 + CancelToken 撤旧请求 + 序号丢弃过期结果。中间译文只上屏不播报, 原来一句 5 秒的话要打十几次真实服务商调用,还会跟 final 抢链路; - TTS 提到 _updateExistingHistoryItem 最前,不再排在浮窗 / 写历史 / 记统计那串 await 之后; - startRecognition 先 await onInit 那次 changeTranslationMode,堵掉 "进页面立刻点开始 → 落到默认 zh-CN/en-US"的竞态。 AuthInterceptor 的 DioExceptionType.cancel 分支不再弹红色"请求已取消" ——取消永远是客户端自己发起的,不改的话中间结果一接上就会一路狂弹。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
1 month ago |
|
|
2d1ee0b783 |
apps/client 首次入库(Flutter 移动端)
从 /Users/yunyanzhineng/work/eaimar_client-main 搬入,未带原 .git,历史留在原仓库。 3215 个文件 / 约 118MB,其中约 40MB 是构建必需的预编译 SDK(azure_speech 的 MicrosoftCognitiveServicesSpeech.xcframework、device_jieli 的 JL_BLEKit 等)。 build/ (5.2G)、ios/Pods/ (311M)、android/.gradle/ (120M) 已由 .gitignore 挡掉。 本次入库的代码包含两项刚做完的改动: 1) 游客登录入口接上服务端开关。此前 _checkGuestLoginConfig() 恒定写死 true, 入口根本不读配置。现改为取服务端算好的结论,并把入口所在的 Builder 改成 Obx ——配置是异步拿到的,Builder 只渲染 onInit 时的初值 false,入口再也不会出现。 拿不到配置时按隐藏处理:默认显示会在送审期把不该露的入口露给审核员。 ⚠️ 已发布的老客户端关不掉(写死 true),只对本版及以后生效。 2) iOS 上架合规修复: - 移除 ATT/IDFA:删掉 Info.plist 的 NSUserTrackingUsageDescription、 SKAdNetworkItems、NSAdvertisingAttributionReportEndpoint,删掉 splash 里的 授权请求与 app_tracking_transparency 依赖。本 App 从不读取 IDFA,也没有任何 广告/归因 SDK,弹框纯粹劝退用户,还要在 App Store Connect 申报追踪数据类型。 - ios/Podfile 里那段 config.build_settings.delete('NSUserTrackingUsageDescription') 是无效代码(该 key 在 Info.plist 里,不是 build setting),换成注释说明。 - 通讯录符号(ITMS-90683 被拒原因)随之一并解决:permission_handler 走 SPM 后 按 Info.plist 有无对应 key 决定编不编,判定结果被 SwiftPM 按内容哈希缓存, 改完 plist 必须清 DerivedData 与 manifests 缓存才会重新求值。 另附 GOOGLE_PLAY_RELEASE_CHECKLIST.md:Google Play 上架阻塞项的实测梳理 (targetSdk 36 与 Billing 8 的 2026-08-31 期限、16KB 页对齐不合规的两个 .so、 支付回退逻辑的下架风险)。 ⚠️ android/app/sign/ 下的 eaimar.keystore 与 Eaimar.properties(含明文签名密码) 随本次提交入库,与本仓库"私有仓库下接受真实凭据"的既有取舍一致。 仓库若要公开,这两个必须先摘出去。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
1 month ago |