```
feat(DiaryQuickSheet): 添加AI追问问答功能和底部协作入口 - 新增currentAnswer状态管理追问输入,添加answerFocused状态独立处理键盘避让 - 移除键盘工具条的关心条,将AI协作入口固定到底部按钮 - 添加完整的问答式追问卡片组件,支持自由回答输入和加入日记功能 - 修改prompt阶段行为,不再在正文区显示邀请横幅 - 更新recordCurrent为answerCurrent,实现问题+答案一同加入日记的逻辑 - 调整底部操作栏布局,间距和内边距优化 refactor(InferenceSettingsView): 性能自检改为内联展开模式 - 将性能自检视图从导航链接改为当前页就地展开 - 添加showSelfTest状态控制展开收起动画 - 支持ModelSelfTestView内联嵌入模式,去除外层导航和背景 chore(Localizable): 同步更新本地化字符串资源 - 添加新的UI文本:加入日记、在这儿写下你的回答、康康帮你一起填等 - 修复部分字符串位置调整和翻译映射问题 - 同步更新多语言版本的翻译内容 style(RootView): 优化记一笔标签页视觉设计 - 为记一笔标签添加语音识别角标标识 - 使用麦克风图标配合加号突出长按语音直达功能 ```
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
|
||||
/// 把一段补充文本追加到正文末尾(自动补换行,空文本忽略)。
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user