feat: streaming for llm summary generation (#2)
This commit is contained in:
@@ -10,51 +10,62 @@ class NotesGenerator {
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Generates meeting notes from meeting data using template-based system prompt
|
||||
/// Generates meeting notes from meeting data using template-based system prompt with streaming
|
||||
/// - Parameters:
|
||||
/// - meeting: The meeting object containing all necessary data
|
||||
/// - userBlurb: Information about the user for context
|
||||
/// - systemPrompt: The system prompt template with placeholders
|
||||
/// - Returns: Generated meeting notes
|
||||
func generateNotes(meeting: Meeting,
|
||||
userBlurb: String,
|
||||
systemPrompt: String) async throws -> String {
|
||||
|
||||
guard let apiKey = KeychainHelper.shared.get(forKey: "openAIKey"), !apiKey.isEmpty else {
|
||||
throw NSError(domain: "NotesGenerator", code: 1, userInfo: [NSLocalizedDescriptionKey: "OpenAI API key not configured"])
|
||||
}
|
||||
|
||||
let openAI = OpenAI(apiToken: apiKey)
|
||||
|
||||
// Create date formatter for meeting date
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .full
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
// Prepare template variables
|
||||
let templateVariables: [String: String] = [
|
||||
"meeting_title": meeting.title.isEmpty ? "Untitled Meeting" : meeting.title,
|
||||
"meeting_date": dateFormatter.string(from: meeting.date),
|
||||
"transcript": meeting.formattedTranscript,
|
||||
"user_blurb": userBlurb,
|
||||
"user_notes": meeting.userNotes
|
||||
]
|
||||
|
||||
// Process the system prompt template
|
||||
let systemContent = Settings.processTemplate(systemPrompt, with: templateVariables)
|
||||
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
|
||||
/// - Returns: AsyncStream of partial generated notes
|
||||
func generateNotesStream(meeting: Meeting,
|
||||
userBlurb: String,
|
||||
systemPrompt: String) -> AsyncStream<String> {
|
||||
return AsyncStream { continuation in
|
||||
Task {
|
||||
do {
|
||||
guard let apiKey = KeychainHelper.shared.get(forKey: "openAIKey"), !apiKey.isEmpty else {
|
||||
continuation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
let openAI = OpenAI(apiToken: apiKey)
|
||||
|
||||
// Create date formatter for meeting date
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .full
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
// Prepare template variables
|
||||
let templateVariables: [String: String] = [
|
||||
"meeting_title": meeting.title.isEmpty ? "Untitled Meeting" : meeting.title,
|
||||
"meeting_date": dateFormatter.string(from: meeting.date),
|
||||
"transcript": meeting.formattedTranscript,
|
||||
"user_blurb": userBlurb,
|
||||
"user_notes": meeting.userNotes
|
||||
]
|
||||
|
||||
// Process the system prompt template
|
||||
let systemContent = Settings.processTemplate(systemPrompt, with: templateVariables)
|
||||
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
|
||||
|
||||
print(systemContent)
|
||||
|
||||
let query = ChatQuery(messages: [systemMessage], model: .gpt4_1)
|
||||
|
||||
let result = try await openAI.chats(query: query)
|
||||
|
||||
guard let generatedNotes = result.choices.first?.message.content else {
|
||||
throw NSError(domain: "NotesGenerator", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to generate notes"])
|
||||
print(systemContent)
|
||||
|
||||
let query = ChatQuery(messages: [systemMessage], model: .gpt4_1)
|
||||
|
||||
let stream: AsyncThrowingStream<ChatStreamResult, Error> = openAI.chatsStream(query: query)
|
||||
|
||||
for try await result in stream {
|
||||
if let content = result.choices.first?.delta.content {
|
||||
continuation.yield(content)
|
||||
}
|
||||
}
|
||||
|
||||
continuation.finish()
|
||||
} catch {
|
||||
print("Error in streaming generation: \(error)")
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return generatedNotes
|
||||
}
|
||||
|
||||
/// Validates if OpenAI API key is configured
|
||||
|
||||
@@ -148,17 +148,25 @@ class MeetingViewModel: ObservableObject {
|
||||
isGeneratingNotes = true
|
||||
errorMessage = nil
|
||||
|
||||
// Clear existing notes for streaming
|
||||
meeting.generatedNotes = ""
|
||||
|
||||
do {
|
||||
// Load settings for generation
|
||||
let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
|
||||
let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt()
|
||||
|
||||
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes(
|
||||
// Use streaming generation
|
||||
let stream = NotesGenerator.shared.generateNotesStream(
|
||||
meeting: meeting,
|
||||
userBlurb: userBlurb,
|
||||
systemPrompt: systemPrompt
|
||||
)
|
||||
|
||||
for await chunk in stream {
|
||||
meeting.generatedNotes += chunk
|
||||
}
|
||||
|
||||
saveMeeting()
|
||||
} catch {
|
||||
errorMessage = "Failed to generate notes: \(error.localizedDescription)"
|
||||
|
||||
Reference in New Issue
Block a user