Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions apps/desktop/src/components/editor-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Editor, { type TiptapEditor } from "@hypr/tiptap/editor";
import Renderer from "@hypr/tiptap/renderer";
import { extractHashtags } from "@hypr/tiptap/shared";
import { cn } from "@hypr/ui/lib/utils";
import { markdownTransform, modelProvider, smoothStream, streamText } from "@hypr/utils/ai";
import { generateText, markdownTransform, modelProvider, providerName, smoothStream, streamText } from "@hypr/utils/ai";
import { useOngoingSession, useSession } from "@hypr/utils/contexts";
import { enhanceFailedToast } from "../toast/shared";
import { FloatingButton } from "./floating-button";
Expand Down Expand Up @@ -52,9 +52,14 @@ export default function EditorArea({
[sessionId, showRaw],
);

const generateTitle = useGenerateTitleMutation({ sessionId });
const enhance = useEnhanceMutation({
sessionId,
rawContent,
onSuccess: (content) => {
console.log("useEnhanceMutation onSuccess", content);
generateTitle.mutate({ enhancedContent: content });
},
});

useAutoEnhance({
Expand Down Expand Up @@ -165,9 +170,11 @@ export default function EditorArea({
export function useEnhanceMutation({
sessionId,
rawContent,
onSuccess,
}: {
sessionId: string;
rawContent: string;
onSuccess: (enhancedContent: string) => void;
}) {
const { userId, onboardingSessionId } = useHypr();

Expand Down Expand Up @@ -246,6 +253,13 @@ export function useEnhanceMutation({
markdownTransform(),
smoothStream({ delayInMs: 80, chunking: "line" }),
],
providerOptions: {
[providerName]: {
metadata: {
grammar: "enhance",
},
},
},
});

let acc = "";
Expand All @@ -257,7 +271,9 @@ export function useEnhanceMutation({

return text.then(miscCommands.opinionatedMdToHtml);
},
onSuccess: () => {
onSuccess: (enhancedContent) => {
onSuccess(enhancedContent ?? "");

analyticsCommands.event({
event: sessionId === onboardingSessionId
? "onboarding_enhance_done"
Expand All @@ -280,7 +296,63 @@ export function useEnhanceMutation({
return enhance;
}

export function useAutoEnhance({
function useGenerateTitleMutation({ sessionId }: { sessionId: string }) {
const { title, updateTitle } = useSession(sessionId, (s) => ({
title: s.session.title,
updateTitle: s.updateTitle,
}));

const generateTitle = useMutation({
mutationKey: ["generateTitle", sessionId],
mutationFn: async ({ enhancedContent }: { enhancedContent: string }) => {
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");

const newTitle = await generateText({
abortSignal,
model,
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: userMessage },
],
providerOptions: {
[providerName]: {
metadata: {
grammar: "title",
},
},
},
});

if (!title) {
updateTitle(newTitle.text);
}
},
});

return generateTitle;
}

function useAutoEnhance({
sessionId,
enhanceStatus,
enhanceMutate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ interface NoteHeaderProps {
hashtags?: string[];
}

export function NoteHeader({ onNavigateToEditor, editable, sessionId, hashtags = [] }: NoteHeaderProps) {
export function NoteHeader(
{ onNavigateToEditor, editable, sessionId, hashtags = [] }: NoteHeaderProps,
) {
const updateTitle = useSession(sessionId, (s) => s.updateTitle);
const sessionTitle = useSession(sessionId, (s) => s.session.title);

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/locales/ko/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""

Expand Down
3 changes: 3 additions & 0 deletions crates/gbnf/assets/title.gbnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
char ::= [A-Za-z0-9]
start ::= [A-Z0-9]
root ::= start char* (" " char+)*
26 changes: 25 additions & 1 deletion crates/gbnf/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
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<Vec<String>>),
Title,
}

impl GBNF {
pub fn build(&self) -> String {
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();

for (input, expected) in vec![
("Meeting Summary", true),
("Product Review Discussion", true),
("A", true),
("Planning Session", true),
("Q1 Planning Session", true),
("meeting summary", false),
("Meeting-Summary", false),
("", false),
] {
let result = gbnf.validate(TITLE, input).unwrap();
assert_eq!(result, expected, "failed: {}", input);
}
}

#[test]
fn test_enhance_grammar() {
let input_1 = "<headers>\n- Objective\n- Key Takeaways\n- Importance of Complementary Skills\n- Benefits of Using Online Resources\n- Advice for Undergrad Students\n</headers># 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! {"
<headers>
Expand Down
26 changes: 2 additions & 24 deletions crates/template/assets/create_title.system.jinja
Original file line number Diff line number Diff line change
@@ -1,24 +1,2 @@
You are a professional assistant that generates a refined title for a meeting note in {{ config.general.display_language | language }}.

# Inputs Provided by the user:

- Meeting Information (txt)
- Raw Note (markdown)
- Meeting Transcript (txt)

# Your Task:

1. Analyze the provided content and transcript thoroughly.
2. Create a more precise, informative, and engaging title that accurately reflects the main topics and outcomes of the meeting.
3. Ensure the new title is concise yet comprehensive, ideally not exceeding 10 words.
4. If the original title is already optimal, you may suggest keeping it as is.

Respond with only the refined title, without any additional explanation or formatting. Respond in JSON.

# Output Structure:

```json
{
"title": string
}
```
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.
26 changes: 4 additions & 22 deletions crates/template/assets/create_title.user.jinja
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# Meeting Information:
<note>
{{ enhanced_note }}
</note>

<meeting_info>
{% 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 %}
</meeting_info>

# Raw Note:

<raw_note>
{{ pre_meeting_editor }}
{{ in_meeting_editor }}
</raw_note>

# Meeting Transcript:

<transcript>
{{ render_timeline_view(timeline_view=timeline_view) }}
</transcript>
Now, give me SUPER CONCISE title for above note. Only about the topic of the meeting.
22 changes: 22 additions & 0 deletions crates/template/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PredefinedTemplate> for Template {
Expand All @@ -43,12 +47,20 @@ impl From<PredefinedTemplate> 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);
Expand All @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion packages/utils/src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ export const useChat = (options: Parameters<typeof useChat$1>[0]) => {
});
};

export const providerName = "hypr-llm";

const getModel = async ({ onboarding }: { onboarding: boolean }) => {
const getter = onboarding ? connectorCommands.getLocalLlmConnection : connectorCommands.getLlmConnection;
const { type, connection: { api_base, api_key } } = await getter();

const openai = createOpenAICompatible({
name: "hypr-llm",
name: providerName,
baseURL: api_base,
apiKey: api_key ?? "SOMETHING_NON_EMPTY",
fetch: customFetch,
Expand Down
Loading
Loading