From 30f75dc2cd79968d246ce6d5d01fd7567ed981fa Mon Sep 17 00:00:00 2001 From: link2026 Date: Wed, 17 Jun 2026 10:05:32 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(DiaryQuickSheet):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0AI=E8=BF=BD=E9=97=AE=E9=97=AE=E7=AD=94=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=92=8C=E5=BA=95=E9=83=A8=E5=8D=8F=E4=BD=9C=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增currentAnswer状态管理追问输入,添加answerFocused状态独立处理键盘避让 - 移除键盘工具条的关心条,将AI协作入口固定到底部按钮 - 添加完整的问答式追问卡片组件,支持自由回答输入和加入日记功能 - 修改prompt阶段行为,不再在正文区显示邀请横幅 - 更新recordCurrent为answerCurrent,实现问题+答案一同加入日记的逻辑 - 调整底部操作栏布局,间距和内边距优化 refactor(InferenceSettingsView): 性能自检改为内联展开模式 - 将性能自检视图从导航链接改为当前页就地展开 - 添加showSelfTest状态控制展开收起动画 - 支持ModelSelfTestView内联嵌入模式,去除外层导航和背景 chore(Localizable): 同步更新本地化字符串资源 - 添加新的UI文本:加入日记、在这儿写下你的回答、康康帮你一起填等 - 修复部分字符串位置调整和翻译映射问题 - 同步更新多语言版本的翻译内容 style(RootView): 优化记一笔标签页视觉设计 - 为记一笔标签添加语音识别角标标识 - 使用麦克风图标配合加号突出长按语音直达功能 ``` --- 康康/Features/Diary/DiaryQuickSheet.swift | 230 +++++++++++-------- 康康/Features/Me/InferenceSettingsView.swift | 32 ++- 康康/Features/Me/ModelSelfTestView.swift | 82 ++++--- 康康/Localizable.xcstrings | 114 +++++---- 康康/RootView.swift | 10 + 5 files changed, 278 insertions(+), 190 deletions(-) diff --git a/康康/Features/Diary/DiaryQuickSheet.swift b/康康/Features/Diary/DiaryQuickSheet.swift index f609e46..7ac9b9c 100644 --- a/康康/Features/Diary/DiaryQuickSheet.swift +++ b/康康/Features/Diary/DiaryQuickSheet.swift @@ -31,6 +31,8 @@ struct DiaryQuickSheet: View { } @State private var phase: AssistPhase = .idle @State private var questions: [DiaryAssistService.Question] = [] + /// 当前这道追问的「自由回答」输入。采纳(加入日记)或跳过、切到下一题时清空。 + @State private var currentAnswer: String = "" @State private var lastRate: Double = 0 @State private var currentRound: Int = 0 /// 累积已覆盖的问诊维度(question.dim),回传下一轮 prompt 用于按维度去重。 @@ -45,6 +47,9 @@ struct DiaryQuickSheet: View { /// 仍保留 medium,用户可手动下拉收回为半屏(纯写文本时更轻量)。 @State private var detent: PresentationDetent = .large @FocusState private var contentFocused: Bool + /// 追问答案输入框的聚焦态。与 contentFocused 分开:答题时聚焦的是答案框、不是正文框, + /// 两者各自独立避让键盘,互不打架。 + @FocusState private var answerFocused: Bool // MARK: 语音输入状态(spec 2026-06-10-voice-diary) @@ -184,12 +189,6 @@ struct DiaryQuickSheet: View { RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) .strokeBorder(Tj.Palette.line, lineWidth: 1) ) - // ①「关心条」主舞台:贴键盘正上方,随写随冒、一次只问一句。 - .toolbar { - ToolbarItemGroup(placement: .keyboard) { - careBarRow(compact: true) - } - } if voicePhase != .idle { DiaryVoicePanel( @@ -248,11 +247,39 @@ struct DiaryQuickSheet: View { } .scrollDismissesKeyboard(.interactively) - HStack(spacing: 12) { + HStack(spacing: 8) { Button("取消") { dismiss() } - .buttonStyle(TjGhostButton(height: 44, fontSize: 15, horizontalPadding: 18)) + .buttonStyle(TjGhostButton(height: 44, fontSize: 15, horizontalPadding: 14)) + // 「康康帮你一起填」:AI 协作主入口,移到底部和保存同一行(spec 2026-06-17)。 + // 中间 flexible 占主宽、最醒目;想想中时禁用并显示进度脉冲。 + Button(action: requestSuggestions) { + HStack(spacing: 5) { + Image(systemName: "sparkles") + .font(.tjScaled( 13, weight: .semibold)) + .symbolEffect(.pulse, options: .repeating, isActive: isLoading) + Text(isLoading ? String(appLoc: "想想中…") : String(appLoc: "康康帮你一起填")) + .font(.tjScaled( 14, weight: .semibold)) + .lineLimit(1) + .minimumScaleFactor(0.8) + } + .foregroundStyle(canRequestSuggest ? Tj.Palette.brick : Tj.Palette.text3) + .frame(maxWidth: .infinity) + .frame(height: 44) + .background( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .fill(Tj.Palette.brick.opacity(canRequestSuggest ? 0.12 : 0.05)) + ) + .overlay( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .strokeBorder(Tj.Palette.brick.opacity(canRequestSuggest ? 0.4 : 0.15), + lineWidth: 1) + ) + .contentShape(RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)) + } + .buttonStyle(.plain) + .disabled(!canRequestSuggest) Button("保存") { submit() } - .buttonStyle(TjPrimaryButton(height: 44, fontSize: 15, horizontalPadding: 18)) + .buttonStyle(TjPrimaryButton(height: 44, fontSize: 15, horizontalPadding: 14)) .disabled(!canSubmit) .opacity(canSubmit ? 1 : 0.4) } @@ -321,12 +348,10 @@ struct DiaryQuickSheet: View { VStack(alignment: .leading, spacing: 10) { if !contentFocused { switch careState { - case .hidden: + case .hidden, .prompt: + // prompt(有内容、还没生成)不在正文区显示卡片:AI 入口已常驻底部 + //「康康帮你一起填」按钮,正文区保持干净。 EmptyView() - case .prompt: - // 还没开始协作:醒目的整行邀请 banner(自带标题,不再挂「康康帮你记」抬头, - // 免得两行都在说「帮你记」)。这是「智能协作不明显」的主补强点。 - promptBanner default: // 已在协作(想想 / 追问 / 问完 / 失败):挂抬头 + tok/s + 关心条卡片。 VStack(alignment: .leading, spacing: 10) { @@ -365,49 +390,6 @@ struct DiaryQuickSheet: View { .animation(.snappy(duration: 0.22), value: contentFocused) } - /// 「还没让康康帮忙」时的醒目邀请 banner(正文里的主入口)。 - /// 比键盘上那条小胶囊更有存在感:圆形图标 + 标题 + 一句副说明 + 箭头, - /// 让「这里有个本地 AI 协作」一眼可见(对齐目标:智能协作要明显)。 - private var promptBanner: some View { - Button(action: requestSuggestions) { - HStack(spacing: 11) { - Image(systemName: "sparkles") - .font(.tjScaled( 15, weight: .semibold)) - .foregroundStyle(Tj.Palette.paper) - .frame(width: 32, height: 32) - .background(Circle().fill(Tj.Palette.brick)) - VStack(alignment: .leading, spacing: 2) { - Text("让康康帮你把这条记得更全") - .font(.tjScaled( 14, weight: .semibold)) - .foregroundStyle(Tj.Palette.text) - Text("从医生问诊角度提几个值得补充的细节 · 本机推理") - .font(.tjScaled( 11)) - .foregroundStyle(Tj.Palette.text3) - .lineLimit(1) - } - Spacer(minLength: 0) - Image(systemName: "arrow.right") - .font(.tjScaled( 13, weight: .semibold)) - .foregroundStyle(Tj.Palette.brick) - } - .padding(.horizontal, 14) - .padding(.vertical, 12) - .frame(maxWidth: .infinity, alignment: .leading) - .background( - RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) - .fill(Tj.Palette.brick.opacity(0.08)) - ) - .overlay( - RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) - .strokeBorder(Tj.Palette.brick.opacity(0.45), lineWidth: 1) - ) - .contentShape(RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)) - } - .buttonStyle(.plain) - .disabled(!canRequestSuggest) - .opacity(canRequestSuggest ? 1 : 0.5) - } - /// 关心条的统一渲染。`compact = true` 给键盘正上方那条(单行紧凑); /// `compact = false` 给正文回落卡(问题可换两行、留白更松)。两处共用同一 careState 与动作。 @ViewBuilder @@ -454,28 +436,8 @@ struct DiaryQuickSheet: View { } case .asking(let q): - HStack(spacing: 10) { - Image(systemName: "text.bubble.fill") - .font(.tjScaled( compact ? 12 : 13, weight: .semibold)) - .foregroundStyle(Tj.Palette.brick) - Text(q.q) - .font(.tjScaled( compact ? 13 : 14, weight: .medium)) - .foregroundStyle(Tj.Palette.text) - .lineLimit(compact ? 1 : 2) - .fixedSize(horizontal: false, vertical: !compact) - Spacer(minLength: 6) - Button { skipCurrent(q) } label: { - Text("跳过") - .font(.tjScaled( 12, weight: .semibold)) - .foregroundStyle(Tj.Palette.text3) - } - .buttonStyle(.plain) - Button { recordCurrent(q) } label: { - careCapsule(icon: "plus", text: String(appLoc: "记一下"), - tint: Tj.Palette.ink, style: .filled, compact: compact) - } - .buttonStyle(.plain) - } + // 键盘工具条已移除,asking 始终落在正文里的「问答卡片」。 + askingCard(q) case .caughtUp(let exhausted): Button(action: requestSuggestions) { @@ -512,7 +474,7 @@ struct DiaryQuickSheet: View { private enum CareCapsuleStyle { case filled, soft } - /// 关心条里的胶囊。filled = 实心(主动作「记一下」);soft = 浅色底(邀请类)。 + /// 关心条里的胶囊。filled = 实心强调;soft = 浅色底(邀请类,prompt / caughtUp 用)。 private func careCapsule(icon: String, text: String, tint: Color, style: CareCapsuleStyle, compact: Bool) -> some View { HStack(spacing: 5) { @@ -529,6 +491,80 @@ struct DiaryQuickSheet: View { .contentShape(Capsule()) } + /// 当前答案去掉首尾空白(判空 / 提交用)。 + private var answerTrimmed: String { + currentAnswer.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private func answerPlaceholder(for q: DiaryAssistService.Question) -> String { + q.dim.isEmpty + ? String(appLoc: "在这儿写下你的回答…") + : String(appLoc: "说说「\(q.dim)」的情况…") + } + + /// 「问答式」追问卡片(正文回落卡里):康康问一句 → 用户自由打字回答 → + /// 「加入日记」把「· 维度:答案」追加到正文。问题与答案都完整留痕(spec 2026-06-17)。 + @ViewBuilder + private func askingCard(_ q: DiaryAssistService.Question) -> some View { + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .firstTextBaseline, spacing: 8) { + Image(systemName: "text.bubble.fill") + .font(.tjScaled( 13, weight: .semibold)) + .foregroundStyle(Tj.Palette.brick) + Text(q.q) + .font(.tjScaled( 14, weight: .medium)) + .foregroundStyle(Tj.Palette.text) + .fixedSize(horizontal: false, vertical: true) + Spacer(minLength: 6) + if pendingQuestions.count > 1 { + Text(String(format: String(appLoc: "还有 %d 个"), pendingQuestions.count - 1)) + .font(.tjScaled( 10, weight: .medium)) + .foregroundStyle(Tj.Palette.text3) + } + } + TextField(answerPlaceholder(for: q), text: $currentAnswer, axis: .vertical) + .font(.tjScaled( 13)) + .lineLimit(1...4) + .focused($answerFocused) + .submitLabel(.done) + .padding(.horizontal, 12) + .padding(.vertical, 9) + .background( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .fill(Tj.Palette.sand2) + ) + .overlay( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .strokeBorder(answerFocused ? Tj.Palette.brick.opacity(0.5) : Tj.Palette.line, + lineWidth: 1) + ) + HStack(spacing: 10) { + Button { skipCurrent(q) } label: { + Text("跳过这条") + .font(.tjScaled( 13, weight: .semibold)) + .foregroundStyle(Tj.Palette.text3) + } + .buttonStyle(.plain) + Spacer(minLength: 0) + Button { answerCurrent(q) } label: { + HStack(spacing: 5) { + Image(systemName: "text.append") + .font(.tjScaled( 12, weight: .semibold)) + Text("加入日记") + .font(.tjScaled( 13, weight: .semibold)) + } + .foregroundStyle(Tj.Palette.paper) + .padding(.horizontal, 14) + .padding(.vertical, 8) + .background(Capsule().fill(answerTrimmed.isEmpty ? Tj.Palette.text3 : Tj.Palette.ink)) + .contentShape(Capsule()) + } + .buttonStyle(.plain) + .disabled(answerTrimmed.isEmpty) + } + } + } + // MARK: - Actions private func sectionLabel(_ text: String) -> some View { @@ -709,11 +745,12 @@ struct DiaryQuickSheet: View { /// 要求本轮避开这些维度,从结构上压住跨轮换皮重复。 /// 进入推理即**收起键盘**:把舞台让给正文里的协作卡片,让「康康在想想」+ 彩色呼吸条 /// 的本地推理过程完整可见(键盘挡住卡片时呼吸条就白跑了,正是「推理时没有呼吸条」的成因)。 - /// 出结果后点「记一下」会自动重新聚焦续写(recordCurrent 里 contentFocused = true), - /// 书写节奏照样接得上。 + /// 出结果后在正文卡片以「问答卡片」呈现:用户自由回答、点「加入日记」把 + ///「· 维度:答案」追加进正文(answerCurrent),问题与答案一并留痕。 private func requestSuggestions() { suggestTask?.cancel() contentFocused = false + answerFocused = false let snapshotContent = content.trimmingCharacters(in: .whitespacesAndNewlines) let covered = Array(coveredDims) exhaustedNote = false @@ -789,25 +826,30 @@ struct DiaryQuickSheet: View { phase = hasQuestions ? .ready : .idle } - /// 「记一下」:把这条问题对应的补充句落进正文,标记已采纳,关心条自动滑到下一句。 - /// 用 `assemble(values: [])` 取「去方括号、占位回退为原词」的干净句子—— - /// 即便用户不再细填,也不会把 `[时间]` 这种机器括号留进日记。 - private func recordCurrent(_ question: DiaryAssistService.Question) { - let stub = question.fill.isEmpty - ? question.q - : DiaryFillTemplate.assemble(question.fill, values: []) - appendToContent(stub) + /// 「加入日记」:把用户对这道追问的【自由回答】按「· 维度:答案」追加到正文, + /// 标记已采纳,关心条自动滑到下一题(答案框清空、保持聚焦,顺势接着答)。 + /// 维度名取自 question.dim(受控词表「起病诱因/症状性质/…」);模型偶尔没给 dim + /// 时退化为「· 答案」。这是「问题+答案一起进日记」的落点(spec 2026-06-17)。 + private func answerCurrent(_ question: DiaryAssistService.Question) { + let answer = currentAnswer.trimmingCharacters(in: .whitespacesAndNewlines) + guard !answer.isEmpty else { return } + let dim = question.dim.trimmingCharacters(in: .whitespacesAndNewlines) + appendToContent(dim.isEmpty ? "· \(answer)" : "· \(dim):\(answer)") if let idx = questions.firstIndex(where: { $0.id == question.id }) { questions[idx].adopted = true } - // 落字后把键盘留住:用户顺势接着写,关心条已切到下一句。 - contentFocused = true + currentAnswer = "" + // 还有下一题就把答案框留住、顺势接着答;答完了收键盘,把整理好的正文交还用户。 + if pendingQuestions.isEmpty { answerFocused = false } } - /// 「跳过」:这句先不记,关心条滑到下一句。该维度已在生成时计入 coveredDims, - /// 下一轮 prompt 不会再问它,所以跳过的不必从 questions 里删。 + /// 「跳过这条」:这句先不记,关心条滑到下一句。该维度已在生成时计入 coveredDims, + /// 下一轮 prompt 不会再问它,所以跳过的不必从 questions 里删。清空当前答案输入; + /// 队列清空(没有下一题了)则收起键盘。 private func skipCurrent(_ question: DiaryAssistService.Question) { skippedQuestionIDs.insert(question.id) + currentAnswer = "" + if pendingQuestions.isEmpty { answerFocused = false } } /// 把一段补充文本追加到正文末尾(自动补换行,空文本忽略)。 diff --git a/康康/Features/Me/InferenceSettingsView.swift b/康康/Features/Me/InferenceSettingsView.swift index ae3d8b1..70b6c0a 100644 --- a/康康/Features/Me/InferenceSettingsView.swift +++ b/康康/Features/Me/InferenceSettingsView.swift @@ -5,6 +5,8 @@ import SwiftUI struct InferenceSettingsView: View { @AppStorage("kk.inferenceEngine") private var engineRaw = EnginePreference.auto.rawValue @State private var modelService = ModelDownloadService.shared + /// 性能自检改为当前页就地展开,不再 push 新页面。 + @State private var showSelfTest = false private var selected: EnginePreference { EnginePreference(rawValue: engineRaw) ?? .auto @@ -48,19 +50,27 @@ struct InferenceSettingsView: View { @ViewBuilder private var selfTestSection: some View { if modelReady { - NavigationLink { - ModelSelfTestView() - } label: { - HStack(spacing: 8) { - Image(systemName: "gauge.with.needle") - .font(.tjScaled(15, weight: .semibold)) - Text("性能自检") - Image(systemName: "arrow.right") - .font(.tjScaled(13, weight: .semibold)) + VStack(spacing: 12) { + Button { + withAnimation(.easeInOut(duration: 0.22)) { showSelfTest.toggle() } + } label: { + HStack(spacing: 8) { + Image(systemName: "gauge.with.needle") + .font(.tjScaled(15, weight: .semibold)) + Text("性能自检") + Image(systemName: "chevron.down") + .font(.tjScaled(13, weight: .semibold)) + .rotationEffect(.degrees(showSelfTest ? 180 : 0)) + } + .frame(maxWidth: .infinity) + } + .buttonStyle(TjGhostButton()) + + if showSelfTest { + ModelSelfTestView(embedded: true) + .transition(.opacity.combined(with: .move(edge: .top))) } - .frame(maxWidth: .infinity) } - .buttonStyle(TjGhostButton()) .padding(.top, 4) } else { VStack(spacing: 8) { diff --git a/康康/Features/Me/ModelSelfTestView.swift b/康康/Features/Me/ModelSelfTestView.swift index b9a429c..ce92dcf 100644 --- a/康康/Features/Me/ModelSelfTestView.swift +++ b/康康/Features/Me/ModelSelfTestView.swift @@ -33,45 +33,55 @@ struct ModelSelfTestView: View { } } + /// 内联模式:嵌进「推理引擎」页就地展开,去掉外层 ScrollView / 背景 / 导航标题。 + var embedded = false + var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - promptCard - - HStack { - Text(phase.label) - .font(.tjScaled( 13, weight: .medium)) - .foregroundStyle(statusColor) - .lineLimit(1) - Spacer() - if rate > 0 { - Text(String(format: "%.1f tok/s", rate)) - .font(.tjScaled( 12, design: .monospaced)) - .foregroundStyle(Tj.Palette.text3) - } - } - - Button { - Task { await run() } - } label: { - Text(isBusy ? "运行中…" : "运行性能自检").frame(maxWidth: .infinity) - } - .buttonStyle(TjPrimaryButton()) - .disabled(isBusy) - - if isBusy { AIFlowBar() } - - if let r = lastResult { statsCard(r) } - - outputCard - - if !history.isEmpty { historyCard } + if embedded { + content + } else { + ScrollView { + content.padding(16) } - .padding(16) + .background(Tj.Palette.sand.ignoresSafeArea()) + .navigationTitle("性能自检") + .navigationBarTitleDisplayMode(.inline) + } + } + + private var content: some View { + VStack(alignment: .leading, spacing: 16) { + promptCard + + HStack { + Text(phase.label) + .font(.tjScaled( 13, weight: .medium)) + .foregroundStyle(statusColor) + .lineLimit(1) + Spacer() + if rate > 0 { + Text(String(format: "%.1f tok/s", rate)) + .font(.tjScaled( 12, design: .monospaced)) + .foregroundStyle(Tj.Palette.text3) + } + } + + Button { + Task { await run() } + } label: { + Text(isBusy ? "运行中…" : "运行性能自检").frame(maxWidth: .infinity) + } + .buttonStyle(TjPrimaryButton()) + .disabled(isBusy) + + if isBusy { AIFlowBar() } + + if let r = lastResult { statsCard(r) } + + outputCard + + if !history.isEmpty { historyCard } } - .background(Tj.Palette.sand.ignoresSafeArea()) - .navigationTitle("性能自检") - .navigationBarTitleDisplayMode(.inline) .onAppear { history = BenchmarkService.load() } } diff --git a/康康/Localizable.xcstrings b/康康/Localizable.xcstrings index 9e231c1..02e11e8 100644 --- a/康康/Localizable.xcstrings +++ b/康康/Localizable.xcstrings @@ -3806,6 +3806,9 @@ } } } + }, + "加入日记" : { + }, "加入记录" : { @@ -4530,6 +4533,9 @@ } } } + }, + "在这儿写下你的回答…" : { + }, "在这里输入主诉……" : { "extractionState" : "stale", @@ -5887,6 +5893,9 @@ }, "康康在想想…" : { + }, + "康康帮你一起填" : { + }, "康康帮你记" : { @@ -6466,6 +6475,9 @@ } } } + }, + "想想中…" : { + }, "慢性肾病" : { "localizations" : { @@ -10034,9 +10046,6 @@ } } } - }, - "用上方选中的引擎跑固定 prompt,实测 prefill / 生成 tok/s" : { - }, "用于自动判定 正常/偏高/偏低" : { "localizations" : { @@ -11211,8 +11220,27 @@ "让康康帮你把这条记得更全" : { }, - "记一下" : { - + "记一笔" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "추가" + } + } + } }, "记剂量与时间" : { @@ -11720,6 +11748,9 @@ }, "说完了,整理成日记" : { + }, + "说说「%@」的情况…" : { + }, "说说你想给医生看什么" : { "extractionState" : "stale", @@ -11938,50 +11969,6 @@ } } }, - "追踪" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracking" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "推移" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "추적" - } - } - } - }, - "记一笔" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Add" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "追加" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "추가" - } - } - } - }, "跟随系统" : { "localizations" : { "en" : { @@ -12005,6 +11992,7 @@ } }, "跳过" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -12028,6 +12016,9 @@ }, "跳过 · 手动录入" : { + }, + "跳过这条" : { + }, "身体档案" : { "localizations" : { @@ -12343,6 +12334,9 @@ }, "还想到几个想问你 · 再来一轮" : { + }, + "还有 %d 个" : { + }, "还没有任何记录\n点底部 + 号开始" : { "localizations" : { @@ -12575,6 +12569,28 @@ } } }, + "追踪" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracking" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "推移" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "추적" + } + } + } + }, "通俗解读:设备本地 AI 把指标与趋势转述为易懂的说明" : { "localizations" : { "en" : { diff --git a/康康/RootView.swift b/康康/RootView.swift index 639e287..c1a8eab 100644 --- a/康康/RootView.swift +++ b/康康/RootView.swift @@ -292,6 +292,16 @@ private struct TabBar: View { .foregroundStyle(Tj.Palette.paper) } .frame(width: slotHeight, height: slotHeight) + // 语音识别角标:体现「长按语音直达」,加号 + 麦克风双标识 + .overlay(alignment: .bottomTrailing) { + Image(systemName: "mic.fill") + .font(.tjScaled( 8, weight: .bold)) + .foregroundStyle(Tj.Palette.paper) + .frame(width: 15, height: 15) + .background(Circle().fill(Tj.Palette.brick)) + .overlay(Circle().strokeBorder(Tj.Palette.paper, lineWidth: 1.5)) + .offset(x: 3, y: 2) + } Text("记一笔") .font(.tjScaled( 11, weight: .semibold))