Skip to content

Commit deab9bf

Browse files
committed
fix(macos): polish onboarding learning letter
Signed-off-by: xunzhuo <xunzhuo@vllm-semantic-router.ai>
1 parent 886831e commit deab9bf

6 files changed

Lines changed: 83 additions & 65 deletions

File tree

apps/macos/Sources/AppLocalization.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ enum AppText {
591591
case .providerNeedsDetails:
592592
return pick(language, en: "Provider and model ID are required; OpenAI Compatible also needs Base URL unless reusing an existing setup.", zh: "请选择模型服务并填写模型 ID;OpenAI Compatible 通常还需要 Base URL,除非复用已有配置。", fr: "Provider et ID de modèle sont requis ; OpenAI Compatible nécessite aussi une Base URL sauf si vous réutilisez une configuration.", de: "Provider und Modell-ID sind erforderlich; OpenAI Compatible braucht zusätzlich eine Base URL, außer du nutzt eine bestehende Konfiguration.")
593593
case .learningTitle:
594-
return pick(language, en: "Building Your Elephant", zh: "正在建立你的 Elephant", fr: "Construction de votre Elephant", de: "Dein Elephant wird aufgebaut")
594+
return pick(language, en: "Building your Personal Model deeply", zh: "深度构建你的个人模型中", fr: "Construction approfondie de votre Personal Model", de: "Dein Personal Model wird vertieft aufgebaut")
595595
case .learningPreparing:
596596
return pick(language, en: "Preparing your first learning pass", zh: "准备第一次学习", fr: "Préparation du premier apprentissage", de: "Ersten Lernlauf vorbereiten")
597597
case .learningCreateModel:

apps/macos/Sources/Views.swift

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -118,47 +118,48 @@ struct OnboardingLetterToast: View {
118118
Button {
119119
model.openOnboardingLetter(entry)
120120
} label: {
121-
HStack(spacing: 14) {
121+
HStack(spacing: 11) {
122122
ZStack {
123-
RoundedRectangle(cornerRadius: 8, style: .continuous)
124-
.fill(ElephantTheme.gold.opacity(0.16))
123+
RoundedRectangle(cornerRadius: 9, style: .continuous)
124+
.fill(ElephantTheme.ember.opacity(0.10))
125125
Image(systemName: "envelope.open.fill")
126-
.font(.system(size: 20, weight: .semibold))
126+
.font(.system(size: 17, weight: .semibold))
127127
.symbolRenderingMode(.hierarchical)
128128
.foregroundStyle(ElephantTheme.ember)
129129
}
130-
.frame(width: 42, height: 42)
130+
.frame(width: 36, height: 36)
131131

132-
VStack(alignment: .leading, spacing: 3) {
132+
VStack(alignment: .leading, spacing: 2) {
133133
Text(onboardingLetterTitle(model.appLanguage))
134134
.font(.callout.weight(.semibold))
135135
.foregroundStyle(ElephantTheme.ink)
136-
Text(localizedYouText(model.appLanguage, en: "Open the first note it wrote after getting to know you.", zh: "打开它认识你之后写下的第一封信。", fr: "Ouvrez sa première note après vous avoir connu.", de: "Öffne die erste Nachricht nach dem Kennenlernen."))
136+
.lineLimit(1)
137+
Text(localizedYouText(model.appLanguage, en: "It is ready to read.", zh: "它已经写好了。", fr: "Elle est prête à lire.", de: "Er ist bereit."))
137138
.font(.caption)
138139
.foregroundStyle(ElephantTheme.muted)
139-
.lineLimit(2)
140+
.lineLimit(1)
140141
}
141142

142143
HStack(spacing: 5) {
143144
Image(systemName: "arrow.right")
144145
.font(.caption2.weight(.bold))
145146
}
146147
.foregroundStyle(ElephantTheme.ember)
147-
.padding(.horizontal, 9)
148-
.padding(.vertical, 7)
148+
.frame(width: 30, height: 30)
149149
.background(Color.white.opacity(0.52), in: Capsule())
150150
.overlay(Capsule().stroke(ElephantTheme.ember.opacity(0.22), lineWidth: 1))
151151
}
152-
.padding(14)
153-
.frame(width: 380, alignment: .leading)
152+
.padding(.horizontal, 12)
153+
.padding(.vertical, 11)
154+
.frame(width: 330, alignment: .leading)
154155
.background(letterToastBackground)
155156
.overlay(
156157
RoundedRectangle(cornerRadius: 12, style: .continuous)
157158
.stroke(hovering ? ElephantTheme.ember.opacity(0.48) : ElephantTheme.gold.opacity(0.28), lineWidth: hovering ? 1.5 : 1)
158159
)
159-
.shadow(color: ElephantTheme.ember.opacity(hovering ? 0.20 : 0.12), radius: hovering ? 24 : 18, y: hovering ? 14 : 10)
160+
.shadow(color: ElephantTheme.ember.opacity(hovering ? 0.16 : 0.09), radius: hovering ? 22 : 16, y: hovering ? 12 : 8)
160161
.contentShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
161-
.scaleEffect(hovering ? 1.012 : 1)
162+
.scaleEffect(hovering ? 1.008 : 1)
162163
}
163164
.buttonStyle(PressablePlainButtonStyle())
164165
.overlay(alignment: .topTrailing) {
@@ -168,11 +169,11 @@ struct OnboardingLetterToast: View {
168169
Image(systemName: "xmark")
169170
.font(.caption.weight(.bold))
170171
.foregroundStyle(ElephantTheme.faint)
171-
.frame(width: 24, height: 24)
172+
.frame(width: 22, height: 22)
172173
.background(.thinMaterial, in: Circle())
173174
}
174175
.buttonStyle(PressablePlainButtonStyle())
175-
.offset(x: 8, y: -8)
176+
.offset(x: 7, y: -7)
176177
.help(localizedYouText(model.appLanguage, en: "Dismiss", zh: "暂不查看", fr: "Ignorer", de: "Schliessen"))
177178
}
178179
.onHover { hovering = $0 }
@@ -237,9 +238,9 @@ struct ElephantLetterEnvelopeOverlay: View {
237238
.padding(.bottom, 34)
238239
.frame(maxWidth: .infinity, alignment: .leading)
239240
}
240-
.frame(maxHeight: 520)
241+
.frame(minHeight: 520, maxHeight: 680)
241242
}
242-
.frame(width: 660)
243+
.frame(width: 720)
243244
.background(letterPaper)
244245
.overlay(letterDecoration)
245246
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
@@ -5118,7 +5119,10 @@ private func displayedOnboardingLetterContent(_ content: String, language: AppLa
51185119
return content
51195120
}
51205121
if isLegacyOnboardingLetterTitle(lines[titleIndex]) {
5121-
lines[titleIndex] = onboardingLetterTitle(language)
5122+
lines.remove(at: titleIndex)
5123+
while let first = lines.first, first.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
5124+
lines.removeFirst()
5125+
}
51225126
}
51235127
return lines.joined(separator: "\n")
51245128
}
@@ -5130,6 +5134,8 @@ private func isLegacyOnboardingLetterTitle(_ line: String) -> Bool {
51305134
.lowercased()
51315135
guard normalized.contains("elephant") else { return false }
51325136
return (normalized.contains("first") && normalized.contains("letter"))
5137+
|| (normalized.contains("letter") && normalized.contains("from"))
5138+
|| normalized.contains("一封信")
51335139
|| normalized.contains("第一封信")
51345140
|| normalized.contains("première lettre")
51355141
|| normalized.contains("erster brief")
@@ -21417,9 +21423,9 @@ struct OnboardingLearningLivePanel: View {
2141721423
var statusText: String
2141821424

2141921425
var body: some View {
21420-
VStack(spacing: 14) {
21426+
VStack(spacing: 12) {
2142121427
OnboardingLearningAnimation()
21422-
.frame(width: 88, height: 88)
21428+
.frame(width: 62, height: 62)
2142321429
.accessibilityHidden(true)
2142421430

2142521431
VStack(spacing: 8) {
@@ -21474,7 +21480,7 @@ struct OnboardingLearningLivePanel: View {
2147421480
.frame(maxWidth: 540)
2147521481
}
2147621482
.padding(.horizontal, 26)
21477-
.padding(.vertical, 22)
21483+
.padding(.vertical, 20)
2147821484
.frame(maxWidth: .infinity, minHeight: 442, alignment: .center)
2147921485
.background(learningPanelBackground)
2148021486
.overlay(RoundedRectangle(cornerRadius: 18, style: .continuous).stroke(Color.white.opacity(0.50), lineWidth: 1))
@@ -21508,27 +21514,25 @@ struct OnboardingLearningLivePanel: View {
2150821514
}
2150921515

2151021516
private var latestToolItem: OnboardingLearningToolTraceItem? {
21511-
guard let event = job?.toolProgress.events.last else { return nil }
21512-
let phase = resolvedToolPhase(event)
21513-
return OnboardingLearningToolTraceItem(
21514-
toolID: event.toolID,
21515-
phase: phase,
21516-
name: toolDisplayName(event.toolID),
21517-
detail: toolPhaseDisplayName(phase),
21518-
preview: event.preview,
21519-
symbol: toolPhaseSymbol(phase),
21520-
tint: toolPhaseTint(phase)
21521-
)
21517+
guard let events = job?.toolProgress.events else { return nil }
21518+
for event in events.reversed() {
21519+
let phase = resolvedToolPhase(event)
21520+
guard !isHiddenToolPhase(phase) else { continue }
21521+
return OnboardingLearningToolTraceItem(
21522+
toolID: event.toolID,
21523+
phase: phase,
21524+
name: toolDisplayName(event.toolID),
21525+
detail: toolPhaseDisplayName(phase),
21526+
preview: event.preview,
21527+
symbol: toolPhaseSymbol(phase),
21528+
tint: toolPhaseTint(phase)
21529+
)
21530+
}
21531+
return nil
2152221532
}
2152321533

2152421534
private var panelSubtitle: String {
21525-
if !modelProgress.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
21526-
return localizedYouText(model.appLanguage, en: "Elephant is turning your answers into the first usable memory.", zh: "Elephant 正在把你的回答整理成第一版可用记忆。", fr: "Elephant transforme vos réponses en première mémoire.", de: "Elephant macht aus deinen Antworten die erste Erinnerung.")
21527-
}
21528-
if latestToolItem != nil {
21529-
return localizedYouText(model.appLanguage, en: "Following one live signal at a time.", zh: "正在跟随一个实时信号,不打断你的开始体验。", fr: "Suit un signal en direct.", de: "Folgt einem Live-Signal.")
21530-
}
21531-
return localizedYouText(model.appLanguage, en: "Keep this as one calm moment while setup finishes.", zh: "最后一步保持成一个安静的建立时刻。", fr: "Un moment calme pendant la fin de configuration.", de: "Ein ruhiger Moment, während die Einrichtung endet.")
21535+
localizedYouText(model.appLanguage, en: "This usually takes 3 to 5 minutes.", zh: "需要 3 到 5 分钟", fr: "Cela prend généralement 3 à 5 minutes.", de: "Das dauert meist 3 bis 5 Minuten.")
2153221536
}
2153321537

2153421538
private var stageTitle: String {
@@ -21608,6 +21612,11 @@ struct OnboardingLearningLivePanel: View {
2160821612
return event.phase
2160921613
}
2161021614

21615+
private func isHiddenToolPhase(_ phase: String) -> Bool {
21616+
let normalized = phase.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
21617+
return normalized.contains("failed") || normalized.contains("error") || normalized.contains("cancel")
21618+
}
21619+
2161121620
private func toolDisplayName(_ tool: String) -> String {
2161221621
switch tool.replacingOccurrences(of: "tool.", with: "") {
2161321622
case "personal_model.search":
@@ -21771,9 +21780,9 @@ struct OnboardingLearningToolTimeline: View {
2177121780
return "\(title) · \(detail)"
2177221781
}
2177321782

21774-
private var liveContentHeight: CGFloat { 88 }
21783+
private var liveContentHeight: CGFloat { 118 }
2177521784

21776-
private var timelineHeight: CGFloat { 144 }
21785+
private var timelineHeight: CGFloat { 174 }
2177721786

2177821787
private var activityScrollKey: String {
2177921788
"\(modelProgress.text)|\(latestToolItem?.id ?? "")|\(job?.progressStage ?? "")|\(job?.progressDetail ?? "")"
@@ -21819,7 +21828,7 @@ struct OnboardingLearningToolTimeline: View {
2181921828
case "tool_completed":
2182021829
return localizedYouText(model.appLanguage, en: "One step completed", zh: "已完成一步", fr: "Une étape terminée", de: "Ein Schritt abgeschlossen")
2182121830
case "tool_failed":
21822-
return localizedYouText(model.appLanguage, en: "One step needs attention", zh: "有一步需要处理", fr: "Une étape à vérifier", de: "Ein Schritt braucht Prüfung")
21831+
return localizedYouText(model.appLanguage, en: "Continuing the build", zh: "继续整理信息", fr: "Construction en cours", de: "Aufbau läuft weiter")
2182321832
case "result_written":
2182421833
return localizedYouText(model.appLanguage, en: "Writing the learning result", zh: "正在写入学习结果", fr: "Écriture du résultat", de: "Schreibt Ergebnis")
2182521834
case "completed":
@@ -21855,6 +21864,7 @@ struct OnboardingLearningToolTimeline: View {
2185521864
let toolID = event.toolID.trimmingCharacters(in: .whitespacesAndNewlines)
2185621865
guard !toolID.isEmpty else { continue }
2185721866
let phase = resolvedToolPhase(event, progress: progress)
21867+
guard !isHiddenToolPhase(phase) else { continue }
2185821868
let key = "\(toolID)-\(phase)-\(event.preview)"
2185921869
guard !seen.contains(key) else { continue }
2186021870
seen.insert(key)
@@ -21924,6 +21934,11 @@ struct OnboardingLearningToolTimeline: View {
2192421934
return event.phase
2192521935
}
2192621936

21937+
private func isHiddenToolPhase(_ phase: String) -> Bool {
21938+
let normalized = phase.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
21939+
return normalized.contains("failed") || normalized.contains("error") || normalized.contains("cancel")
21940+
}
21941+
2192721942
private func toolPhaseDisplayName(_ phase: String) -> String {
2192821943
switch phase {
2192921944
case "requested":
@@ -22204,13 +22219,9 @@ struct OnboardingLearningAnimation: View {
2220422219
context.fill(Path(ellipseIn: rect), with: .color(palette[index % palette.count].opacity(0.78)))
2220522220
}
2220622221

22207-
let iconRect = CGRect(x: center.x - 30, y: center.y - 30, width: 60, height: 60)
22208-
context.fill(Path(roundedRect: iconRect, cornerRadius: 8), with: .color(ElephantTheme.accent.opacity(0.10)))
22209-
context.stroke(Path(roundedRect: iconRect, cornerRadius: 8), with: .color(ElephantTheme.accent.opacity(0.22)), lineWidth: 1)
22210-
}
22211-
.overlay {
22212-
BrandMark(size: 62, framed: true)
22213-
.shadow(color: ElephantTheme.accent.opacity(0.16), radius: 18, y: 8)
22222+
let core = CGRect(x: center.x - 8, y: center.y - 8, width: 16, height: 16)
22223+
context.fill(Path(ellipseIn: core), with: .color(ElephantTheme.accent.opacity(0.22)))
22224+
context.fill(Path(ellipseIn: core.insetBy(dx: 4, dy: 4)), with: .color(ElephantTheme.accent.opacity(0.62)))
2221422225
}
2221522226
}
2221622227
}

packages/reflect/evidence.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,8 @@ def _letter_evidence_use_lines() -> tuple[str, ...]:
337337
"- Treat the portrait below as grounding evidence, not copy to paste into the letter.",
338338
"- Synthesize repeated facts once. Do not list the user's traits like a dashboard or psychological report.",
339339
"- Do not mention raw lens names, PM topics, field IDs, demographic fragments, avatar paths, or schema language.",
340-
"- Warmth should come from specific attention to rhythm, pressure/recovery style, current focus, values, tastes, and hopes.",
340+
"- Warmth should come from specific attention to rhythm, pressure/recovery style, current focus, values, tastes, hopes, and the tension underneath the facts.",
341+
"- Prefer two or three deeper observations over many shallow facts. Name what the user may be trying to protect, carry, or move toward when the evidence supports it.",
341342
"- Elephant may promise to remember this beginning and keep useful traces, but must not pretend a long shared history that is not in evidence.",
342343
)
343344

@@ -614,7 +615,7 @@ def build_evidence(
614615
*(portrait or ("(no facts yet)",)),
615616
"",
616617
"## Product promise to weave into the letter",
617-
"AI is becoming more capable, many people worry about being replaced, and Elephant's answer is: 别怕,我们一同进化. Write this as Elephant's own promise: I remember the path we start walking, I keep the useful traces, and I evolve with you rather than replacing you.",
618+
"AI is becoming more capable, and many people quietly worry about being replaced, flattened, or forced to speed up. Do not drop a slogan. Write this as Elephant's own promise: I keep memory for the user, help them stay close to what matters, and grow beside them rather than replacing them.",
618619
])
619620

620621
return "\n".join(lines)

0 commit comments

Comments
 (0)