From 3d81c84a6e2b2b24f9562e46129ab6d083288e2d Mon Sep 17 00:00:00 2001 From: plyght Date: Fri, 13 Jun 2025 02:34:00 -0400 Subject: [PATCH 1/7] init title gen --- .../src/components/editor-area/index.tsx | 104 +++++++++++++++++ .../editor-area/note-header/index.tsx | 6 +- .../editor-area/note-header/title-input.tsx | 41 +++++-- crates/gbnf/assets/title.gbnf | 1 + crates/gbnf/src/lib.rs | 25 +++- .../template/assets/create_title.user.jinja | 26 +---- crates/template/src/lib.rs | 22 ++++ plugins/local-llm/src/lib.rs | 107 ++++++++++++++++++ plugins/local-llm/src/server.rs | 19 +++- 9 files changed, 315 insertions(+), 36 deletions(-) create mode 100644 crates/gbnf/assets/title.gbnf diff --git a/apps/desktop/src/components/editor-area/index.tsx b/apps/desktop/src/components/editor-area/index.tsx index fa3d96debe..599779ff81 100644 --- a/apps/desktop/src/components/editor-area/index.tsx +++ b/apps/desktop/src/components/editor-area/index.tsx @@ -57,6 +57,11 @@ export default function EditorArea({ rawContent, }); + const generateTitle = useGenerateTitleMutation({ + sessionId, + enhancedContent, + }); + useAutoEnhance({ sessionId, enhanceStatus: enhance.status, @@ -83,6 +88,18 @@ export default function EditorArea({ enhance.mutate(); }, [enhance]); + const updateTitle = useSession(sessionId, (s) => s.updateTitle); + + const handleGenerateTitle = useCallback(() => { + generateTitle.mutate(undefined, { + onSuccess: (title) => { + if (title) { + updateTitle(title); + } + }, + }); + }, [generateTitle, updateTitle]); + const safelyFocusEditor = useCallback(() => { if (editorRef.current?.editor && editorRef.current.editor.isEditable) { requestAnimationFrame(() => { @@ -108,6 +125,8 @@ export default function EditorArea({ editable={editable} onNavigateToEditor={safelyFocusEditor} hashtags={hashtags} + onGenerateTitle={handleGenerateTitle} + isGeneratingTitle={generateTitle.status === "pending"} />
{ + if (!enhancedContent || enhancedContent.trim() === "") { + return null; + } + + const config = await dbCommands.getConfig(); + const { type } = await connectorCommands.getLlmConnection(); + + const systemMessage = await templateCommands.render( + "create_title.system", + { config, type }, + ); + + const userMessage = await templateCommands.render( + "create_title.user", + { + type, + enhanced_note: enhancedContent, + }, + ); + + const abortController = new AbortController(); + const abortSignal = AbortSignal.any([abortController.signal, AbortSignal.timeout(30 * 1000)]); + + const provider = await modelProvider(); + const model = provider.languageModel("defaultModel"); + + analyticsCommands.event({ + event: "title_generation_start", + distinct_id: userId, + session_id: sessionId, + }); + + const { text } = streamText({ + abortSignal, + model, + messages: [ + { role: "system", content: systemMessage }, + { role: "user", content: userMessage }, + ], + }); + + const result = await text; + + try { + const parsed = JSON.parse(result); + return parsed.title || result; + } catch { + return result; + } + }, + onSuccess: (title) => { + if (title) { + analyticsCommands.event({ + event: "title_generation_done", + distinct_id: userId, + session_id: sessionId, + }); + } + }, + onError: (error) => { + console.error("Title generation failed:", error); + toast({ + title: "Title generation failed", + content: "Failed to generate title for the note", + dismissible: true, + duration: 5000, + }); + }, + }); + + return generateTitle; +} + export function useAutoEnhance({ sessionId, enhanceStatus, diff --git a/apps/desktop/src/components/editor-area/note-header/index.tsx b/apps/desktop/src/components/editor-area/note-header/index.tsx index 614a0a65d9..e5f63479fa 100644 --- a/apps/desktop/src/components/editor-area/note-header/index.tsx +++ b/apps/desktop/src/components/editor-area/note-header/index.tsx @@ -12,9 +12,11 @@ interface NoteHeaderProps { editable?: boolean; sessionId: string; hashtags?: string[]; + onGenerateTitle?: () => void; + isGeneratingTitle?: boolean; } -export function NoteHeader({ onNavigateToEditor, editable, sessionId, hashtags = [] }: NoteHeaderProps) { +export function NoteHeader({ onNavigateToEditor, editable, sessionId, hashtags = [], onGenerateTitle, isGeneratingTitle }: NoteHeaderProps) { const updateTitle = useSession(sessionId, (s) => s.updateTitle); const sessionTitle = useSession(sessionId, (s) => s.session.title); @@ -34,6 +36,8 @@ export function NoteHeader({ onNavigateToEditor, editable, sessionId, hashtags = value={sessionTitle} onChange={handleTitleChange} onNavigateToEditor={onNavigateToEditor} + onGenerateTitle={onGenerateTitle} + isGeneratingTitle={isGeneratingTitle} />
diff --git a/apps/desktop/src/components/editor-area/note-header/title-input.tsx b/apps/desktop/src/components/editor-area/note-header/title-input.tsx index a7bfca5367..c9304b7592 100644 --- a/apps/desktop/src/components/editor-area/note-header/title-input.tsx +++ b/apps/desktop/src/components/editor-area/note-header/title-input.tsx @@ -1,4 +1,5 @@ import { useLingui } from "@lingui/react/macro"; +import { SparklesIcon } from "lucide-react"; import { type ChangeEvent, type KeyboardEvent } from "react"; interface TitleInputProps { @@ -6,6 +7,8 @@ interface TitleInputProps { onChange: (e: ChangeEvent) => void; onNavigateToEditor?: () => void; editable?: boolean; + onGenerateTitle?: () => void; + isGeneratingTitle?: boolean; } export default function TitleInput({ @@ -13,6 +16,8 @@ export default function TitleInput({ onChange, onNavigateToEditor, editable, + onGenerateTitle, + isGeneratingTitle, }: TitleInputProps) { const { t } = useLingui(); @@ -24,15 +29,31 @@ export default function TitleInput({ }; return ( - +
+ + + {editable && onGenerateTitle && ( + + )} +
); } diff --git a/crates/gbnf/assets/title.gbnf b/crates/gbnf/assets/title.gbnf new file mode 100644 index 0000000000..ed9dc64872 --- /dev/null +++ b/crates/gbnf/assets/title.gbnf @@ -0,0 +1 @@ +root ::= [A-Z] ([A-Za-z] | " ")* diff --git a/crates/gbnf/src/lib.rs b/crates/gbnf/src/lib.rs index e81b5537ee..2145280360 100644 --- a/crates/gbnf/src/lib.rs +++ b/crates/gbnf/src/lib.rs @@ -1,7 +1,10 @@ pub const ENHANCE_AUTO: &str = include_str!("../assets/enhance-auto.gbnf"); pub const ENHANCE_TEMPLATE: &str = include_str!("../assets/enhance-template.gbnf"); +pub const TITLE: &str = include_str!("../assets/title.gbnf"); + pub enum GBNF { Enhance(Option>), + Title, } impl GBNF { @@ -9,16 +12,36 @@ impl GBNF { match self { GBNF::Enhance(Some(_)) => ENHANCE_TEMPLATE.to_string(), GBNF::Enhance(None) => ENHANCE_AUTO.to_string(), + GBNF::Title => TITLE.to_string(), } } } #[cfg(test)] mod tests { + use super::*; use indoc::indoc; #[test] - fn test_1() { + fn test_title_grammar() { + let gbnf = gbnf_validator::Validator::new().unwrap(); + + // Valid titles (letters and spaces only) + assert!(gbnf.validate(TITLE, "Meeting Summary").unwrap()); + assert!(gbnf.validate(TITLE, "Product Review Discussion").unwrap()); + assert!(gbnf.validate(TITLE, "A").unwrap()); + assert!(gbnf.validate(TITLE, "Planning Session").unwrap()); + + // Invalid titles (should fail) + assert!(!gbnf.validate(TITLE, "meeting summary").unwrap()); // lowercase start + assert!(!gbnf.validate(TITLE, "Meeting-Summary").unwrap()); // hyphen + assert!(!gbnf.validate(TITLE, "Meeting123").unwrap()); // numbers + assert!(!gbnf.validate(TITLE, "Q1 Planning Session").unwrap()); // numbers + assert!(!gbnf.validate(TITLE, "").unwrap()); // empty + } + + #[test] + fn test_enhance_grammar() { let input_1 = "\n- Objective\n- Key Takeaways\n- Importance of Complementary Skills\n- Benefits of Using Online Resources\n- Advice for Undergrad Students\n# Objective\n\n- **Search is the Best Way to Find Answers**: The speaker emphasizes the importance of utilizing online resources like Google to find answers to questions.\n- **Value in Complementary Skills**: The speaker highlights the need to acquire complementary skills to traditional research methods.\n\n# Key Takeaways\n\n- **Complementary skills include both traditional research and online resource utilization**: The speaker suggests that skills like using a blank sheet of paper with no Internet and effective Google searching are essential.\n- **Online resources can help find pre-solved problems**: The speaker advises investing time in finding existing resources and communities that have already solved problems.\n\n# Importance of Complementary Skills\n\n- **Traditional research is just the starting point**: The speaker suggests that traditional research methods are just the beginning and should be complemented with other skills.\n- **Effective use of online resources can save time and effort**: The speaker highlights the benefits of utilizing online resources in research and problem-solving.\n\n# Benefits of Using Online Resources\n\n- **Access to knowledge from experts and communities**: The speaker suggests that online resources provide access to knowledge and expertise from experienced individuals.\n- **Time-saving and efficient**: The speaker emphasizes the benefits of finding pre-solved problems through online resources.\n\n# Advice for Undergrad Students\n\n- **Start by searching online**: The speaker advises undergrad students to start by searching online for answers to questions and exploring different resources.\n- **Be open to finding existing solutions**: The speaker emphasizes the importance of being open to finding pre-solved problems and leveraging existing resources.\n\n"; let input_2 = indoc! {" diff --git a/crates/template/assets/create_title.user.jinja b/crates/template/assets/create_title.user.jinja index 307655b1ff..5d88a96e75 100644 --- a/crates/template/assets/create_title.user.jinja +++ b/crates/template/assets/create_title.user.jinja @@ -1,23 +1,5 @@ -# Meeting Information: +# Enhanced Meeting Note: - -{% if event %}Event: {{ event.name }}{% endif %} -{% if participants | length > 0 %}Participants:{% endif %} -{%- for participant in participants %} - -- {{ participant.name }}{% if participant.name == config_profile.full_name %} (this is me){% endif %} - {%- endfor %} - - -# Raw Note: - - -{{ pre_meeting_editor }} -{{ in_meeting_editor }} - - -# Meeting Transcript: - - -{{ render_timeline_view(timeline_view=timeline_view) }} - + +{{ enhanced_note }} + diff --git a/crates/template/src/lib.rs b/crates/template/src/lib.rs index af30e7dcde..2a6623c28f 100644 --- a/crates/template/src/lib.rs +++ b/crates/template/src/lib.rs @@ -34,6 +34,10 @@ pub enum PredefinedTemplate { EnhanceSystem, #[strum(serialize = "enhance.user")] EnhanceUser, + #[strum(serialize = "create_title.system")] + CreateTitleSystem, + #[strum(serialize = "create_title.user")] + CreateTitleUser, } impl From for Template { @@ -43,12 +47,20 @@ impl From for Template { Template::Static(PredefinedTemplate::EnhanceSystem) } PredefinedTemplate::EnhanceUser => Template::Static(PredefinedTemplate::EnhanceUser), + PredefinedTemplate::CreateTitleSystem => { + Template::Static(PredefinedTemplate::CreateTitleSystem) + } + PredefinedTemplate::CreateTitleUser => { + Template::Static(PredefinedTemplate::CreateTitleUser) + } } } } pub const ENHANCE_SYSTEM_TPL: &str = include_str!("../assets/enhance.system.jinja"); pub const ENHANCE_USER_TPL: &str = include_str!("../assets/enhance.user.jinja"); +pub const CREATE_TITLE_SYSTEM_TPL: &str = include_str!("../assets/create_title.system.jinja"); +pub const CREATE_TITLE_USER_TPL: &str = include_str!("../assets/create_title.user.jinja"); pub fn init(env: &mut minijinja::Environment) { env.set_unknown_method_callback(minijinja_contrib::pycompat::unknown_method_callback); @@ -60,6 +72,16 @@ pub fn init(env: &mut minijinja::Environment) { .unwrap(); env.add_template(PredefinedTemplate::EnhanceUser.as_ref(), ENHANCE_USER_TPL) .unwrap(); + env.add_template( + PredefinedTemplate::CreateTitleSystem.as_ref(), + CREATE_TITLE_SYSTEM_TPL, + ) + .unwrap(); + env.add_template( + PredefinedTemplate::CreateTitleUser.as_ref(), + CREATE_TITLE_USER_TPL, + ) + .unwrap(); env.add_filter("timeline", filters::timeline); env.add_filter("language", filters::language); diff --git a/plugins/local-llm/src/lib.rs b/plugins/local-llm/src/lib.rs index c211f16f0e..20371d9066 100644 --- a/plugins/local-llm/src/lib.rs +++ b/plugins/local-llm/src/lib.rs @@ -109,6 +109,30 @@ mod test { } } + fn title_generation_request() -> CreateChatCompletionRequest { + use async_openai::types::{ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs}; + + CreateChatCompletionRequest { + messages: vec![ + ChatCompletionRequestMessage::System( + ChatCompletionRequestSystemMessageArgs::default() + .content("You are a professional assistant that generates a refined title for a meeting note in English.") + .build() + .unwrap() + .into(), + ), + ChatCompletionRequestMessage::User( + ChatCompletionRequestUserMessageArgs::default() + .content("# Enhanced Meeting Note:\n\n\n# Project Planning\n- Discussed Q1 roadmap\n- Reviewed budget allocations\n- Set team responsibilities\n") + .build() + .unwrap() + .into(), + ), + ], + ..Default::default() + } + } + #[tokio::test] #[ignore] // cargo test test_non_streaming_response -p tauri-plugin-local-llm -- --ignored --nocapture @@ -181,4 +205,87 @@ mod test { assert!(content.contains("Seoul")); } + + #[tokio::test] + #[ignore] + // cargo test test_title_generation_non_streaming -p tauri-plugin-local-llm -- --ignored --nocapture + async fn test_title_generation_non_streaming() { + let app = create_app(tauri::test::mock_builder()); + app.start_server().await.unwrap(); + let api_base = app.api_base().await.unwrap(); + + let client = reqwest::Client::new(); + + let response = client + .post(format!("{}/chat/completions", api_base)) + .json(&CreateChatCompletionRequest { + stream: Some(false), + ..title_generation_request() + }) + .send() + .await + .unwrap(); + + let data = response + .json::() + .await + .unwrap(); + + let content = data.choices[0].message.content.clone().unwrap(); + println!("Generated title: {}", content); + + // Title should start with capital letter and contain only letters/spaces + assert!(!content.is_empty()); + assert!(content.chars().next().unwrap().is_uppercase()); + assert!(content.chars().all(|c| c.is_alphabetic() || c.is_whitespace())); + } + + #[tokio::test] + #[ignore] + // cargo test test_title_generation_streaming -p tauri-plugin-local-llm -- --ignored --nocapture + async fn test_title_generation_streaming() { + let app = create_app(tauri::test::mock_builder()); + app.start_server().await.unwrap(); + let api_base = app.api_base().await.unwrap(); + + let client = reqwest::Client::new(); + + let response = client + .post(format!("{}/chat/completions", api_base)) + .json(&CreateChatCompletionRequest { + stream: Some(true), + ..title_generation_request() + }) + .send() + .await + .unwrap(); + + let stream = response.bytes_stream().map(|chunk| { + chunk.map(|data| { + let text = String::from_utf8_lossy(&data); + let stripped = text.split("data: ").collect::>()[1]; + let c: CreateChatCompletionStreamResponse = serde_json::from_str(stripped).unwrap(); + c.choices + .first() + .unwrap() + .delta + .content + .as_ref() + .unwrap() + .clone() + }) + }); + + let content = stream + .filter_map(|r| async move { r.ok() }) + .collect::() + .await; + + println!("Generated title (streaming): {}", content); + + // Title should start with capital letter and contain only letters/spaces + assert!(!content.is_empty()); + assert!(content.chars().next().unwrap().is_uppercase()); + assert!(content.chars().all(|c| c.is_alphabetic() || c.is_whitespace())); + } } diff --git a/plugins/local-llm/src/server.rs b/plugins/local-llm/src/server.rs index 631d5742c0..58fe6aa46d 100644 --- a/plugins/local-llm/src/server.rs +++ b/plugins/local-llm/src/server.rs @@ -223,10 +223,25 @@ fn build_response( .map(hypr_llama::FromOpenAI::from_openai) .collect(); + // Detect if this is a title generation request by checking system message content + let is_title_request = request.messages.iter().any(|msg| { + match msg { + async_openai::types::ChatCompletionRequestMessage::System(system_msg) => { + system_msg.content.contains("generates a refined title") + } + _ => false, + } + }); + + let grammar = if is_title_request { + Some(hypr_gbnf::GBNF::Title.build()) + } else { + Some(hypr_gbnf::GBNF::Enhance(Some(vec!["".to_string()])).build()) + }; + let request = hypr_llama::LlamaRequest { messages, - // TODO: should not hard-code this - grammar: Some(hypr_gbnf::GBNF::Enhance(Some(vec!["".to_string()])).build()), + grammar, }; Ok(Box::pin(model.generate_stream(request)?)) From 097b4d78522bc338f0dbb53c351838bff1af9c03 Mon Sep 17 00:00:00 2001 From: plyght Date: Fri, 13 Jun 2025 02:41:58 -0400 Subject: [PATCH 2/7] fix: updates --- .../src/components/editor-area/index.tsx | 3 ++- .../editor-area/note-header/index.tsx | 4 +++- .../editor-area/note-header/title-input.tsx | 8 ++++---- apps/desktop/src/locales/en/messages.po | 2 +- apps/desktop/src/locales/ko/messages.po | 2 +- crates/gbnf/src/lib.rs | 4 ++-- plugins/local-llm/src/lib.rs | 18 ++++++++++++------ plugins/local-llm/src/server.rs | 15 +++++---------- 8 files changed, 30 insertions(+), 26 deletions(-) diff --git a/apps/desktop/src/components/editor-area/index.tsx b/apps/desktop/src/components/editor-area/index.tsx index 599779ff81..fbdf233d39 100644 --- a/apps/desktop/src/components/editor-area/index.tsx +++ b/apps/desktop/src/components/editor-area/index.tsx @@ -353,7 +353,7 @@ export function useGenerateTitleMutation({ }); const result = await text; - + try { const parsed = JSON.parse(result); return parsed.title || result; @@ -373,6 +373,7 @@ export function useGenerateTitleMutation({ onError: (error) => { console.error("Title generation failed:", error); toast({ + id: "title-generation-error", title: "Title generation failed", content: "Failed to generate title for the note", dismissible: true, diff --git a/apps/desktop/src/components/editor-area/note-header/index.tsx b/apps/desktop/src/components/editor-area/note-header/index.tsx index e5f63479fa..64b2fadc16 100644 --- a/apps/desktop/src/components/editor-area/note-header/index.tsx +++ b/apps/desktop/src/components/editor-area/note-header/index.tsx @@ -16,7 +16,9 @@ interface NoteHeaderProps { isGeneratingTitle?: boolean; } -export function NoteHeader({ onNavigateToEditor, editable, sessionId, hashtags = [], onGenerateTitle, isGeneratingTitle }: NoteHeaderProps) { +export function NoteHeader( + { onNavigateToEditor, editable, sessionId, hashtags = [], onGenerateTitle, isGeneratingTitle }: NoteHeaderProps, +) { const updateTitle = useSession(sessionId, (s) => s.updateTitle); const sessionTitle = useSession(sessionId, (s) => s.session.title); diff --git a/apps/desktop/src/components/editor-area/note-header/title-input.tsx b/apps/desktop/src/components/editor-area/note-header/title-input.tsx index c9304b7592..196669ea5e 100644 --- a/apps/desktop/src/components/editor-area/note-header/title-input.tsx +++ b/apps/desktop/src/components/editor-area/note-header/title-input.tsx @@ -40,7 +40,7 @@ export default function TitleInput({ className="flex-1 border-none bg-transparent text-2xl font-bold focus:outline-none placeholder:text-neutral-400" onKeyDown={handleKeyDown} /> - + {editable && onGenerateTitle && ( )} diff --git a/apps/desktop/src/locales/en/messages.po b/apps/desktop/src/locales/en/messages.po index a3a6c09701..53cf80639d 100644 --- a/apps/desktop/src/locales/en/messages.po +++ b/apps/desktop/src/locales/en/messages.po @@ -1072,7 +1072,7 @@ msgstr "Type to search..." msgid "Ugh! Can't use it!" msgstr "Ugh! Can't use it!" -#: src/components/editor-area/note-header/title-input.tsx:33 +#: src/components/editor-area/note-header/title-input.tsx:39 msgid "Untitled" msgstr "Untitled" diff --git a/apps/desktop/src/locales/ko/messages.po b/apps/desktop/src/locales/ko/messages.po index 09cc6a5efc..142cb226f3 100644 --- a/apps/desktop/src/locales/ko/messages.po +++ b/apps/desktop/src/locales/ko/messages.po @@ -1072,7 +1072,7 @@ msgstr "" msgid "Ugh! Can't use it!" msgstr "" -#: src/components/editor-area/note-header/title-input.tsx:33 +#: src/components/editor-area/note-header/title-input.tsx:39 msgid "Untitled" msgstr "" diff --git a/crates/gbnf/src/lib.rs b/crates/gbnf/src/lib.rs index 2145280360..1bb8c61efe 100644 --- a/crates/gbnf/src/lib.rs +++ b/crates/gbnf/src/lib.rs @@ -25,13 +25,13 @@ mod tests { #[test] fn test_title_grammar() { let gbnf = gbnf_validator::Validator::new().unwrap(); - + // Valid titles (letters and spaces only) assert!(gbnf.validate(TITLE, "Meeting Summary").unwrap()); assert!(gbnf.validate(TITLE, "Product Review Discussion").unwrap()); assert!(gbnf.validate(TITLE, "A").unwrap()); assert!(gbnf.validate(TITLE, "Planning Session").unwrap()); - + // Invalid titles (should fail) assert!(!gbnf.validate(TITLE, "meeting summary").unwrap()); // lowercase start assert!(!gbnf.validate(TITLE, "Meeting-Summary").unwrap()); // hyphen diff --git a/plugins/local-llm/src/lib.rs b/plugins/local-llm/src/lib.rs index 20371d9066..7ecf9d9cae 100644 --- a/plugins/local-llm/src/lib.rs +++ b/plugins/local-llm/src/lib.rs @@ -110,8 +110,10 @@ mod test { } fn title_generation_request() -> CreateChatCompletionRequest { - use async_openai::types::{ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs}; - + use async_openai::types::{ + ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs, + }; + CreateChatCompletionRequest { messages: vec![ ChatCompletionRequestMessage::System( @@ -233,11 +235,13 @@ mod test { let content = data.choices[0].message.content.clone().unwrap(); println!("Generated title: {}", content); - + // Title should start with capital letter and contain only letters/spaces assert!(!content.is_empty()); assert!(content.chars().next().unwrap().is_uppercase()); - assert!(content.chars().all(|c| c.is_alphabetic() || c.is_whitespace())); + assert!(content + .chars() + .all(|c| c.is_alphabetic() || c.is_whitespace())); } #[tokio::test] @@ -282,10 +286,12 @@ mod test { .await; println!("Generated title (streaming): {}", content); - + // Title should start with capital letter and contain only letters/spaces assert!(!content.is_empty()); assert!(content.chars().next().unwrap().is_uppercase()); - assert!(content.chars().all(|c| c.is_alphabetic() || c.is_whitespace())); + assert!(content + .chars() + .all(|c| c.is_alphabetic() || c.is_whitespace())); } } diff --git a/plugins/local-llm/src/server.rs b/plugins/local-llm/src/server.rs index 58fe6aa46d..61019ba14b 100644 --- a/plugins/local-llm/src/server.rs +++ b/plugins/local-llm/src/server.rs @@ -224,13 +224,11 @@ fn build_response( .collect(); // Detect if this is a title generation request by checking system message content - let is_title_request = request.messages.iter().any(|msg| { - match msg { - async_openai::types::ChatCompletionRequestMessage::System(system_msg) => { - system_msg.content.contains("generates a refined title") - } - _ => false, + let is_title_request = request.messages.iter().any(|msg| match msg { + async_openai::types::ChatCompletionRequestMessage::System(system_msg) => { + system_msg.content.contains("generates a refined title") } + _ => false, }); let grammar = if is_title_request { @@ -239,10 +237,7 @@ fn build_response( Some(hypr_gbnf::GBNF::Enhance(Some(vec!["".to_string()])).build()) }; - let request = hypr_llama::LlamaRequest { - messages, - grammar, - }; + let request = hypr_llama::LlamaRequest { messages, grammar }; Ok(Box::pin(model.generate_stream(request)?)) } From 4ac09f845d5c91c1680cedde04eb785cf6bcbc12 Mon Sep 17 00:00:00 2001 From: plyght Date: Fri, 13 Jun 2025 02:49:20 -0400 Subject: [PATCH 3/7] updates: fixes --- .../components/editor-area/note-header/title-input.tsx | 2 +- plugins/local-llm/src/server.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/editor-area/note-header/title-input.tsx b/apps/desktop/src/components/editor-area/note-header/title-input.tsx index 196669ea5e..826288ede0 100644 --- a/apps/desktop/src/components/editor-area/note-header/title-input.tsx +++ b/apps/desktop/src/components/editor-area/note-header/title-input.tsx @@ -40,13 +40,13 @@ export default function TitleInput({ className="flex-1 border-none bg-transparent text-2xl font-bold focus:outline-none placeholder:text-neutral-400" onKeyDown={handleKeyDown} /> - {editable && onGenerateTitle && ( - )} - + ); } diff --git a/crates/template/assets/create_title.system.jinja b/crates/template/assets/create_title.system.jinja index bf8727aa0c..880fac7323 100644 --- a/crates/template/assets/create_title.system.jinja +++ b/crates/template/assets/create_title.system.jinja @@ -1 +1,2 @@ -You are a professional assistant that generates a refined title for a meeting note in {{ config.general.display_language | language }}. +You are a professional assistant that generates a perfect title for a meeting note in {{ config.general.display_language | language }}. +Only output title, nothing else. diff --git a/crates/template/assets/create_title.user.jinja b/crates/template/assets/create_title.user.jinja index 5d88a96e75..887c088210 100644 --- a/crates/template/assets/create_title.user.jinja +++ b/crates/template/assets/create_title.user.jinja @@ -1,5 +1,5 @@ -# Enhanced Meeting Note: - - + {{ enhanced_note }} - + + +Now, give me SUPER CONCISE title for above note. Only about the topic of the meeting.