LLM-Prompt (message.post + sos-stream):
- LANG_INSTRUCTIONS Map raus, ersetzt durch dynamische Instruktion
'Reply in {detectedFromUser} ... fallback: {appLang}'
- Lyra matcht jetzt die Sprache der letzten User-Message (per
detectLang Unicode-Detection); App-Locale ist nur noch Fallback
- Instruktion doppelt eingehängt (Anfang + Ende des System-Prompts)
gegen recency bias bei langen deutschen Prompts
TTS (speak dispatcher + speak-cartesia + speak-elevenlabs):
- Kein 'de'-Default mehr für language. detectLang(text, locale) leitet
Sprache primär aus dem Antwort-Text ab (Arabic/Cyrillic/CJK/Turkish-
Letters), Locale als Fallback
- Cartesia + ElevenLabs: language/language_code nur senden wenn
ableitbar, sonst Provider auto-detect statt erzwungenem 'de'
- speak-cartesia: sonic-2 → sonic-3 (Multi-Lang, war beim Dispatcher-
Fix gestern vergessen worden)
- Google: en-US neutraler Fallback statt de-DE-Bias
Neu: server/utils/detect-lang.ts
149 lines
5.0 KiB
Swift
149 lines
5.0 KiB
Swift
import SwiftUI
|
|
|
|
struct SuperviseView: View {
|
|
@Environment(WizardModel.self) private var model
|
|
|
|
@State private var task: Task<Void, Never>?
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
header
|
|
|
|
Text("Wir schreiben jetzt nur die Supervision-Metadaten auf dein iPhone und starten es neu. Apps, Daten und Logins bleiben erhalten. Das dauert ~60 Sekunden. **Trenne das USB-Kabel nicht.**")
|
|
.foregroundStyle(.secondary)
|
|
|
|
statusBox
|
|
|
|
if model.showAdvancedLogs {
|
|
logViewer
|
|
}
|
|
|
|
Button(model.showAdvancedLogs ? "Details ausblenden" : "Details anzeigen") {
|
|
model.showAdvancedLogs.toggle()
|
|
}
|
|
.buttonStyle(.borderless)
|
|
.foregroundStyle(.secondary)
|
|
|
|
Spacer()
|
|
|
|
navigationBar
|
|
}
|
|
.padding(40)
|
|
.onAppear { startIfNeeded() }
|
|
.onDisappear { task?.cancel() }
|
|
}
|
|
|
|
private var header: some View {
|
|
HStack {
|
|
Image(systemName: "lock.shield")
|
|
.font(.system(size: 30))
|
|
.foregroundStyle(.tint)
|
|
Text("Supervisieren")
|
|
.font(.title).bold()
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusBox: some View {
|
|
if model.supervisionRunning {
|
|
HStack(spacing: 8) {
|
|
ProgressView().controlSize(.small)
|
|
Text("supervise-magic läuft …")
|
|
}
|
|
.padding(10)
|
|
.background(Color.blue.opacity(0.08))
|
|
.cornerRadius(6)
|
|
} else if let err = model.supervisionError {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
Image(systemName: "xmark.octagon")
|
|
.foregroundStyle(.red)
|
|
Text(err).font(.callout)
|
|
}
|
|
.padding(10)
|
|
.background(Color.red.opacity(0.08))
|
|
.cornerRadius(6)
|
|
} else if !model.supervisionLog.isEmpty {
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill").foregroundStyle(.green)
|
|
Text("Supervisieren abgeschlossen.")
|
|
}
|
|
.padding(10)
|
|
.background(Color.green.opacity(0.08))
|
|
.cornerRadius(6)
|
|
}
|
|
}
|
|
|
|
private var logViewer: some View {
|
|
ScrollViewReader { proxy in
|
|
ScrollView {
|
|
LazyVStack(alignment: .leading, spacing: 2) {
|
|
ForEach(Array(model.supervisionLog.enumerated()), id: \.offset) { idx, line in
|
|
Text(line)
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundStyle(line.contains("[stderr]") ? .orange : .secondary)
|
|
.id(idx)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
.background(Color.black.opacity(0.04))
|
|
.cornerRadius(6)
|
|
.frame(maxHeight: 220)
|
|
.onChange(of: model.supervisionLog.count) { _, newCount in
|
|
if newCount > 0 { proxy.scrollTo(newCount - 1, anchor: .bottom) }
|
|
}
|
|
}
|
|
}
|
|
|
|
private var navigationBar: some View {
|
|
HStack {
|
|
Button("Zurück") { model.goTo(.preflight) }
|
|
.buttonStyle(.bordered)
|
|
.disabled(model.supervisionRunning)
|
|
Spacer()
|
|
if model.supervisionError != nil {
|
|
Button("Neu versuchen") { startSupervise() }
|
|
.buttonStyle(.bordered)
|
|
}
|
|
Button("Weiter →") { model.advance() }
|
|
.buttonStyle(.borderedProminent)
|
|
.disabled(model.supervisionRunning || model.supervisionLog.isEmpty || model.supervisionError != nil)
|
|
}
|
|
}
|
|
|
|
private func startIfNeeded() {
|
|
if model.supervisionLog.isEmpty && !model.supervisionRunning && model.supervisionError == nil {
|
|
startSupervise()
|
|
}
|
|
}
|
|
|
|
private func startSupervise() {
|
|
model.supervisionLog = []
|
|
model.supervisionError = nil
|
|
model.supervisionRunning = true
|
|
task?.cancel()
|
|
task = Task { @MainActor in
|
|
do {
|
|
_ = try await SuperviseRunner.supervise(
|
|
organizationName: "ReBreak",
|
|
force: true,
|
|
verbose: model.showAdvancedLogs
|
|
) { line in
|
|
model.supervisionLog.append(line)
|
|
}
|
|
model.supervisionRunning = false
|
|
model.device?.isSupervised = true
|
|
model.device?.supervisorOrgName = "ReBreak"
|
|
// Nach re-supervise ist der MDM-Channel oft weg; Enroll-Step soll
|
|
// deshalb nicht fälschlich übersprungen werden.
|
|
model.device?.isEnrolled = false
|
|
model.device?.enrollmentStatus = nil
|
|
} catch {
|
|
model.supervisionError = error.localizedDescription
|
|
model.supervisionRunning = false
|
|
}
|
|
}
|
|
}
|
|
}
|