Changed the Transcript History/audio transcribe results with tab view

This commit is contained in:
Beingpax 2025-09-28 23:16:36 +05:45
parent a37b95620c
commit 253d385d8e
3 changed files with 331 additions and 242 deletions

View File

@ -30,69 +30,7 @@ struct AudioTranscribeView: View {
// Show current transcription result
if let transcription = transcriptionManager.currentTranscription {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Transcription Result")
.font(.headline)
if let enhancedText = transcription.enhancedText {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Enhanced")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
HStack(spacing: 8) {
AnimatedCopyButton(textToCopy: enhancedText)
AnimatedSaveButton(textToSave: enhancedText)
}
}
Text(enhancedText)
.textSelection(.enabled)
}
Divider()
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Original")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
HStack(spacing: 8) {
AnimatedCopyButton(textToCopy: transcription.text)
AnimatedSaveButton(textToSave: transcription.text)
}
}
Text(transcription.text)
.textSelection(.enabled)
}
} else {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Transcription")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
HStack(spacing: 8) {
AnimatedCopyButton(textToCopy: transcription.text)
AnimatedSaveButton(textToSave: transcription.text)
}
}
Text(transcription.text)
.textSelection(.enabled)
}
}
HStack {
Text("Duration: \(formatDuration(transcription.duration))")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
}
}
.padding()
}
TranscriptionResultView(transcription: transcription)
}
}
}
@ -146,40 +84,6 @@ struct AudioTranscribeView: View {
HStack(spacing: 8) {
Text("Prompt:")
.font(.subheadline)
Menu {
ForEach(enhancementService.allPrompts) { prompt in
Button {
enhancementService.setActivePrompt(prompt)
selectedPromptId = prompt.id
} label: {
HStack {
Image(systemName: prompt.icon.rawValue)
.foregroundColor(.accentColor)
Text(prompt.title)
if selectedPromptId == prompt.id {
Spacer()
Image(systemName: "checkmark")
}
}
}
}
} label: {
HStack {
Text(enhancementService.allPrompts.first(where: { $0.id == selectedPromptId })?.title ?? "Select Prompt")
.foregroundColor(.primary)
Image(systemName: "chevron.down")
.font(.caption)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(Color(.controlBackgroundColor))
)
}
.fixedSize()
.disabled(!isEnhancementEnabled)
}
}
}

View File

@ -1,17 +1,155 @@
import SwiftUI
import SwiftData
enum ContentTab: String, CaseIterable {
case original = "Original"
case enhanced = "Enhanced"
case aiRequest = "AI Request"
}
struct TranscriptionCard: View {
let transcription: Transcription
let isExpanded: Bool
let isSelected: Bool
let onDelete: () -> Void
let onToggleSelection: () -> Void
@State private var isAIRequestExpanded: Bool = false
@State private var selectedTab: ContentTab = .original
private var availableTabs: [ContentTab] {
var tabs = [ContentTab.original]
if transcription.enhancedText != nil {
tabs.append(.enhanced)
}
if transcription.aiRequestSystemMessage != nil || transcription.aiRequestUserMessage != nil {
tabs.append(.aiRequest)
}
return tabs
}
private var hasAudioFile: Bool {
if let urlString = transcription.audioFileURL,
let url = URL(string: urlString),
FileManager.default.fileExists(atPath: url.path) {
return true
}
return false
}
private var copyTextForCurrentTab: String {
switch selectedTab {
case .original:
return transcription.text
case .enhanced:
return transcription.enhancedText ?? transcription.text
case .aiRequest:
var result = ""
if let systemMsg = transcription.aiRequestSystemMessage, !systemMsg.isEmpty {
result += systemMsg
}
if let userMsg = transcription.aiRequestUserMessage, !userMsg.isEmpty {
if !result.isEmpty {
result += "\n\n"
}
result += userMsg
}
return result.isEmpty ? transcription.text : result
}
}
private var originalContentView: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Original")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.secondary)
Text(transcription.text)
.font(.system(size: 15, weight: .regular, design: .default))
.lineSpacing(2)
.textSelection(.enabled)
}
}
private func enhancedContentView(_ enhancedText: String) -> some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 4) {
Image(systemName: "sparkles")
.foregroundColor(.blue)
Text("Enhanced")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.blue)
}
Text(enhancedText)
.font(.system(size: 15, weight: .regular, design: .default))
.lineSpacing(2)
.textSelection(.enabled)
}
}
private var aiRequestContentView: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 6) {
Image(systemName: "paperplane.fill")
.foregroundColor(.purple)
Text("AI Request")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(.purple)
}
if let systemMsg = transcription.aiRequestSystemMessage, !systemMsg.isEmpty {
VStack(alignment: .leading, spacing: 6) {
Text("System Prompt")
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.secondary)
Text(systemMsg)
.font(.system(size: 13, weight: .regular, design: .monospaced))
.lineSpacing(2)
.textSelection(.enabled)
}
}
if let userMsg = transcription.aiRequestUserMessage, !userMsg.isEmpty {
VStack(alignment: .leading, spacing: 6) {
Text("User Message")
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.secondary)
Text(userMsg)
.font(.system(size: 13, weight: .regular, design: .monospaced))
.lineSpacing(2)
.textSelection(.enabled)
}
}
}
}
private struct TabButton: View {
let title: String
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.system(size: 13, weight: isSelected ? .semibold : .medium))
.foregroundColor(isSelected ? .white : .secondary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(isSelected ? Color.accentColor : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(isSelected ? Color.clear : Color.secondary.opacity(0.3), lineWidth: 1)
)
.contentShape(.capsule)
)
}
.buttonStyle(.plain)
.animation(.easeInOut(duration: 0.2), value: isSelected)
}
}
var body: some View {
HStack(spacing: 12) {
// Selection checkbox in macOS style
Toggle("", isOn: Binding(
get: { isSelected },
set: { _ in onToggleSelection() }
@ -19,14 +157,13 @@ struct TranscriptionCard: View {
.toggleStyle(CircularCheckboxStyle())
.labelsHidden()
VStack(alignment: .leading, spacing: 8) {
// Header with date and duration
VStack(alignment: .leading, spacing: 12) {
HStack {
Text(transcription.timestamp, format: .dateTime.month(.abbreviated).day().year().hour().minute())
.font(.system(size: 14, weight: .medium, design: .default))
.foregroundColor(.secondary)
Spacer()
Text(formatTiming(transcription.duration))
.font(.system(size: 14, weight: .medium, design: .default))
.padding(.horizontal, 8)
@ -35,148 +172,90 @@ struct TranscriptionCard: View {
.foregroundColor(.blue)
.cornerRadius(6)
}
// Original text section
VStack(alignment: .leading, spacing: 8) {
if isExpanded {
if availableTabs.count > 1 {
HStack(spacing: 2) {
ForEach(availableTabs, id: \.self) { tab in
TabButton(
title: tab.rawValue,
isSelected: selectedTab == tab,
action: { selectedTab = tab }
)
}
Spacer()
AnimatedCopyButton(textToCopy: copyTextForCurrentTab)
}
.padding(.vertical, 8)
.padding(.horizontal, 4)
}
ScrollView {
VStack(alignment: .leading, spacing: 12) {
switch selectedTab {
case .original:
originalContentView
case .enhanced:
if let enhancedText = transcription.enhancedText {
enhancedContentView(enhancedText)
}
case .aiRequest:
aiRequestContentView
}
}
.padding(.vertical, 8)
}
.frame(maxHeight: 300)
.background(Color(.controlBackgroundColor).opacity(0.5))
.cornerRadius(8)
if hasAudioFile, let urlString = transcription.audioFileURL,
let url = URL(string: urlString) {
Divider()
.padding(.vertical, 8)
AudioPlayerView(url: url)
}
if hasMetadata {
Divider()
.padding(.vertical, 8)
VStack(alignment: .leading, spacing: 10) {
if let powerModeValue = powerModeDisplay(
name: transcription.powerModeName,
emoji: transcription.powerModeEmoji
) {
metadataRow(
icon: "bolt.fill",
label: "Power Mode",
value: powerModeValue
)
}
metadataRow(icon: "hourglass", label: "Audio Duration", value: formatTiming(transcription.duration))
if let modelName = transcription.transcriptionModelName {
metadataRow(icon: "cpu.fill", label: "Transcription Model", value: modelName)
}
if let aiModel = transcription.aiEnhancementModelName {
metadataRow(icon: "sparkles", label: "Enhancement Model", value: aiModel)
}
if let promptName = transcription.promptName {
metadataRow(icon: "text.bubble.fill", label: "Prompt Used", value: promptName)
}
if let duration = transcription.transcriptionDuration {
metadataRow(icon: "clock.fill", label: "Transcription Time", value: formatTiming(duration))
}
if let duration = transcription.enhancementDuration {
metadataRow(icon: "clock.fill", label: "Enhancement Time", value: formatTiming(duration))
}
}
}
} else {
Text(transcription.text)
.font(.system(size: 15, weight: .regular, design: .default))
.lineLimit(isExpanded ? nil : 2)
.lineLimit(2)
.lineSpacing(2)
if isExpanded {
HStack {
Text("Original")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.secondary)
Spacer()
AnimatedCopyButton(textToCopy: transcription.text)
}
}
}
// Enhanced text section (only when expanded)
if isExpanded, let enhancedText = transcription.enhancedText {
Divider()
.padding(.vertical, 8)
VStack(alignment: .leading, spacing: 8) {
Text(enhancedText)
.font(.system(size: 15, weight: .regular, design: .default))
.lineSpacing(2)
HStack {
HStack(spacing: 4) {
Image(systemName: "sparkles")
.foregroundColor(.blue)
Text("Enhanced")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.blue)
}
Spacer()
AnimatedCopyButton(textToCopy: enhancedText)
}
}
}
// NEW: AI Request payload (System + User messages) - folded by default
if isExpanded, (transcription.aiRequestSystemMessage != nil || transcription.aiRequestUserMessage != nil) {
Divider()
.padding(.vertical, 8)
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 6) {
Image(systemName: "paperplane.fill")
.foregroundColor(.purple)
Text("AI Request")
.fontWeight(.semibold)
.foregroundColor(.purple)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.easeInOut) {
isAIRequestExpanded.toggle()
}
}
if isAIRequestExpanded {
VStack(alignment: .leading, spacing: 12) {
if let systemMsg = transcription.aiRequestSystemMessage, !systemMsg.isEmpty {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text("System Prompt")
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.secondary)
Spacer()
AnimatedCopyButton(textToCopy: systemMsg)
}
Text(systemMsg)
.font(.system(size: 13, weight: .regular, design: .monospaced))
.lineSpacing(2)
}
}
if let userMsg = transcription.aiRequestUserMessage, !userMsg.isEmpty {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text("User Message")
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.secondary)
Spacer()
AnimatedCopyButton(textToCopy: userMsg)
}
Text(userMsg)
.font(.system(size: 13, weight: .regular, design: .monospaced))
.lineSpacing(2)
}
}
}
}
}
}
// Audio player (if available)
if isExpanded, let urlString = transcription.audioFileURL,
let url = URL(string: urlString),
FileManager.default.fileExists(atPath: url.path) {
Divider()
.padding(.vertical, 8)
AudioPlayerView(url: url)
}
// Metadata section (when expanded)
if isExpanded && hasMetadata {
Divider()
.padding(.vertical, 8)
VStack(alignment: .leading, spacing: 10) {
if let powerModeValue = powerModeDisplay(
name: transcription.powerModeName,
emoji: transcription.powerModeEmoji
) {
metadataRow(
icon: "bolt.fill",
label: "Power Mode",
value: powerModeValue
)
}
metadataRow(icon: "hourglass", label: "Audio Duration", value: formatTiming(transcription.duration))
if let modelName = transcription.transcriptionModelName {
metadataRow(icon: "cpu.fill", label: "Transcription Model", value: modelName)
}
if let aiModel = transcription.aiEnhancementModelName {
metadataRow(icon: "sparkles", label: "Enhancement Model", value: aiModel)
}
if let promptName = transcription.promptName {
metadataRow(icon: "text.bubble.fill", label: "Prompt Used", value: promptName)
}
if let duration = transcription.transcriptionDuration {
metadataRow(icon: "clock.fill", label: "Transcription Time", value: formatTiming(duration))
}
if let duration = transcription.enhancementDuration {
metadataRow(icon: "clock.fill", label: "Enhancement Time", value: formatTiming(duration))
}
}
}
}
}

View File

@ -0,0 +1,106 @@
import SwiftUI
enum TranscriptionTab: String, CaseIterable {
case original = "Original"
case enhanced = "Enhanced"
}
struct TranscriptionResultView: View {
let transcription: Transcription
@State private var selectedTab: TranscriptionTab = .original
private var availableTabs: [TranscriptionTab] {
var tabs: [TranscriptionTab] = [.original]
if transcription.enhancedText != nil {
tabs.append(.enhanced)
}
return tabs
}
private var textForSelectedTab: String {
switch selectedTab {
case .original:
return transcription.text
case .enhanced:
return transcription.enhancedText ?? ""
}
}
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Transcription Result")
.font(.headline)
if availableTabs.count > 1 {
HStack(spacing: 2) {
ForEach(availableTabs, id: \.self) { tab in
TabButton(
title: tab.rawValue,
isSelected: selectedTab == tab,
action: { selectedTab = tab }
)
}
Spacer()
AnimatedCopyButton(textToCopy: textForSelectedTab)
AnimatedSaveButton(textToSave: textForSelectedTab)
}
.padding(.vertical, 8)
.padding(.horizontal, 4)
} else {
HStack {
Spacer()
AnimatedCopyButton(textToCopy: textForSelectedTab)
AnimatedSaveButton(textToSave: textForSelectedTab)
}
}
ScrollView {
Text(textForSelectedTab)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
HStack {
Text("Duration: \(formatDuration(transcription.duration))")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
}
}
.padding()
}
private func formatDuration(_ duration: TimeInterval) -> String {
let minutes = Int(duration) / 60
let seconds = Int(duration) % 60
return String(format: "%d:%02d", minutes, seconds)
}
private struct TabButton: View {
let title: String
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.system(size: 13, weight: isSelected ? .semibold : .medium))
.foregroundColor(isSelected ? .white : .secondary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(isSelected ? Color.accentColor : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(isSelected ? Color.clear : Color.secondary.opacity(0.3), lineWidth: 1)
)
.contentShape(.capsule)
)
}
.buttonStyle(.plain)
.animation(.easeInOut(duration: 0.2), value: isSelected)
}
}
}